04Mar
Handling GraphQL API Authentication using Auth0 with Hasura Actions
Handling GraphQL API Authentication using Auth0 with Hasura Actions

Introduction

Authentication and Authorization are two of the most basic but complex security features in any app development process. It is most common to interchange them, but they are actually different concepts. Authentication is the process of identifying a user while Authorization determines the routes the authenticated user can access based on their role. GraphQL is an awesome language for writing and querying APIs. Yet, handling Authentication and Authorization might not be easy to walk through most especially for new GraphQL developers. At the basic level, you will have to handle the following:

  • Setting up HTTP server for the GraphQL project
  • Setting up a database (Models, ORM, connection, seeding, migration, and so on)
  • Writing GraphQL Schema and
  • Resolvers
    • Validating schema types
    • hashing passwords
    • generating JWT
  • Integrating schema and resolvers to the GraphQL server
  • Authorization layer that checks user roles/permissions
  • and more…

Working on all these can easily get overwhelming (especially for small projects). In this article, we’re going to demonstrate how we can set up Authentication/Authorization with Hasura and Auth0. First, we’ll set up the Auth0 application, API, and rules. Next, we’ll generate a JWT token from our Auth0 application which will serve as Authorization Header for the client (GraphQL in this case). Finally, we’ll demonstrate how Hasura Actions works; by resolving a custom query that retrieves your Auth0 user profile using an HTTP handler.

Hasura

Hasura is an awesome GraphQL engine for building powerful GraphQL APIs. Hasura is a great choice for me based on the following:

  • It is open-source,
  • has great documentation and resources,
  • delivers fast and instant GraphQL APIs,
  • makes database configuration and deployment seamless,
  • it is very extensible with other platforms/tools such as Auth0, Firebase,
  • the cloud service has great UI/UX and straightforward use for the first time,
  • schema is extensible with remote schemas,
  • you can make custom queries and mutations with actions

Auth0

Auth0 is a cloud identity management platform that offers authentication and authorization as a service. The platform came at a time when developers and organizations were having a hard time securing their services/apps. Auth0 is language/framework-agnostic and very extensible. You just have to connect your application (like we will do with Hasura very soon) and define how you want to identify your users. Some Authentication concerns Auth0 lifted off the backs of developers are:

  • Session and Cookie-Based Authentication
  • Passwordless Authentication
  • Token-based Authentication
  • Multi-factor Authentication
  • Social Authentication

Also, Auth0 documentation is the broadest and extensive documentation ever! It’s just like MDN docs for auth 😅.

  • The core features of the Auth0 platform are:
  • Authorization Code OAuth 2.0
  •  Applications
  • APIs
  • Role-Based Access control (RBAC)
  • User Roles and Permissions
  • Rules

We will be using most of these features while building our demo.

Let’s get started.

Pre-requisites

Though we won’t be writing much code here, you will need the following:

  • JavaScript knowledge
  • Hasura account
  • Heroku account
  • Auth0 account
  • Sign up on Glitch

Setting Up Authentication with Auth0

At the end of this section, we will be able to create the following:

  • JWT token as Authorization Header for the Graphiql client
  • Make Authentication Rules
  • Auth0 API Management token
  • Generate JWT config for Hasura

Let’s get started by creating an Auth0 application. Follow these steps:

  1. Click on the Applications tab on your Auth0 dashboard
Auth0 dashboard - Create Application
Auth0 dashboard – Create Application

2. Choose a name and select “Single Page Web Applications”

Choose an application type
Choose an application type

Awesome! Now we’ve created an Application, let’s go-ahead to create the authentication API.

3. Click “APIs” in the left sidebar and the “Create API”

 Create Authentication API
Create Authentication API

Ensure you take note of the identifier. We are going to be using it much later as an audience in our OAuth2/OIDC workflow. Awesome! Before we test if our application is up and running, we should provide rules for when a user is authenticated. Another core feature of Auth0 is Rules. Rules are JavaScript functions that run when the user is authenticated to the application. They only run once authentication is complete. Rules are JavaScript functions? Yes, they are serverless functions. In other words, when you create a Rule in Auth0, you are in fact making use of a function as a service cloud service model. The function spins up a server only when it’s triggered. Auth0 Rules allows us to do the following:

  • Define Authorization rules using JavaScript
    • You can allow denying access to anyone calling an API
    • Add user rules to tokens
    • Allow access to an API on specific dates
    • and so much more
  • Extend attributes from auth providers beyond what Auth0 provides
  • Authenticate a user and get the user details
  • Request custom API access

Here, we just want to authenticate the users and get the user details. Let’s go ahead and write a setup rule for that.

4. Click on “Rules” on the left sidebar. Give it a name and paste the following lines of code.

function (user, context, callback) {
  const namespace = "https://hasura.io/jwt/claims";
  context.accessToken[namespace] =
    {
      'x-hasura-default-role': 'user',
      // do some custom logic to decide allowed roles
      'x-hasura-allowed-roles': ['user'],
      'x-hasura-user-id': user.user_id
    };
  callback(null, user, context);
}
Auth0 Rules
Auth0 Rules

 

Let’s go through the code snippet. The user an argument is an object that stores the authenticated user information. It comes from the identity provider ( Google, Github, and so on). The context is an object that stores information about the application’s current authentication transaction. Information such as user IP address, user connection medium, the authentication protocol used and so much more. The callback function is going to send modified tokens back to Auth0 or throw an error. It’s important to invoke the callback function, so the function won’t timeout. In line 2, we initialized a namespace variable. A namespace is just an identifier you give to your claim ( a claim is an information that asserts the authenticated user info. It’s represented in key/value pair. It’s the key part).

Note: The namespace can be anything really. It’s recommended.

We need the Namespace, so our claim does not conflict with Auth0 reserved names. That’s why it’s called custom claims 😆 . Then, make use of the context.accessToken[namespace] object to add custom claims to the ID token. In our database, we are going to have a user table and create permissions for 'user'. Finally, we invoke the callback function so the script would not timeout. Great! now let’s create another rule that’ll sync users who log in using auth0 with other users who sign in with other authentication strategies but are in the database.

5. Create another rule and call it sync-rules. Add the following lines of code

function (user, context, callback) {
  const userId = user.user_id;
  const nickname = user.nickname;
  
  const admin_secret = <add hasura admin secret here>;
  const url = <add hasura graphql api here> ;
  const query = `mutation($userId: String!, $nickname: String) {
    insert_users(objects: [{
      id: $userId, name: $nickname
    }], on_conflict: {constraint: users_pkey, update_columns: [last_seen, name]}
    ) {
      affected_rows
    }
  }`;
  const variables = { "userId": userId, "nickname": nickname };
  request.post({
      url: url,
      headers: {'content-type' : 'application/json', 'x-hasura-admin-secret': admin_secret},
      body: JSON.stringify({
        query: query,
        variables: variables
      })
  }, function(error, response, body){
       console.log(body);
       callback(null, user, context);
  });
}

In this code snippet, we write a GraphQL mutation that adds a user by id and name, then checks if they’re new users, if there’s a new user, it adds the user and returns the user object. We just performed an upsert operation (An operation that inserts rows into a database table if they do not already exist, or updates them if they do). Awesome! we are done with Rules. Now let’s get our access token and Auth0 management API token. We will get these values by testing the authentication API we just created.

6. Head back to your Auth0 dashboard and click on “Extensions” on the left sidebar and search for “Auth0 Authentication API Debugger”. Go ahead to authorize it, we’ll be using the API debugger to test our API. You should have something like this:

Auth0 API debugger extension
Auth0 API debugger extension

Ensure you take note of the domain and callback URL on the Configuration tab. We’ll use it in the next step.

7. Click on the 0Auth2/0IDC

OAuth is an identity protocol that does resource access and sharing between two applications without giving away your password. It instead uses authorization tokens to confirm identity. OAuth2 framework is the latest and recommended version of the protocol. OIDC (OpenID Connect) is also an identity protocol that enables third-party applications to verify the identity of users and retrieve user information. It uses JSON web tokens (JWT).

OAUTH2/OIDC
OAUTH2/OIDC

 

Under Settings, add Audience so we can get the access token. The audience here should typically be your frontend application URI. However, since we don’t have that, let’s use our API identifier. Also, check the “Use Audience” switch. The config should look like this.

Auth settings
Auth settings

Awesome! One more thing, let’s add the callback URL from the API Debugger Config to our Application. Head back to “Applications” on the left sidebar, click the application name and add the callback URL here:

Application Configuration
Application Configuration

Ensure you save changes.

8. Head back to the API Debugger and click on OAUTH2/OIDC Login and you should get something like this.

OAUTH login
OAUTH login

You can either Login or create a new account. After providing login details, you should be redirected to the page with your access token.

Access token
Access token

Take note of the access_token, we will be using it with Graphiql client. You should also take a look at the Access Token JSON object.

Almost there!! We are almost done with setting up Auth0 except we haven’t visited the Auth0 Management API. The API gives the backend server access to your Auth0 dashboard.

9. Go to API, click on Auth0 Management API, and then the API Explorer tab

Auth0 management API
Auth0 management API

Also, take note of the JWT token.

Congratulations, you have successfully set up Auth0 for our Hasura application.
In the next section, you’ll need the following:

  • Application domain name
  • Auth0 Management API token
  • API access token

Setting up the Hasura application

Hasura gives you two options to build APIs: Hasura Cloud and Hasura CLI. Hasura Cloud is recommended cause the setup is just seamless though, in bigger production apps, your team might want to use Hasura locally with the CLI. Let’s get started already. In this section, we will achieve the following:

  • Connect Auth0 with Hasura
  • Create custom types and type definition for Auth0
  • Write and Deploy a Rest API that invokes our authentication API
  • Get user profile info
  1. Go to https://cloud.hasura.io and create a project.
Create Hasura App
Create Hasura App

2. Set Environmental variables

Hasura Env Vars
Hasura Env Vars

Already, you have an admin secret provided. Hasura admin secret will make sure our GraphQl endpoint is not public. Let’s add another variable called HASURA_GRAPHQL_JWT_SECRET. This is how we connect our auth server to Hasura. Auth0 is going to return the JWT token, Hasura will decode and verify the token and then authorize the request. To generate this token, head to https://hasura.io/jwt-config/, select auth0 as the provider, then paste your Auth0 application domain as the domain name and click on the “Generate Config” button.

Generated JWT token
Generated JWT token

Now copy the token and add it as value to HASURA_GRAPHQL_JWT config. Awesome! Launch Console

 Hasura Console
Hasura Console

3. Create custom types and type definition for Auth0

At this point, you might be wondering how we are going to extend the Schema (which we don’t have control over) and write custom business logic to integrate the auth0 API endpoint. Normally with GraphQL, we’ll write a Resolver (queries or mutations) to perform this operation. However, Hasura now makes this possible by using Actions. Actions help you write custom queries and mutations. Hasura Actions has 4 components: Type, Type Definition, Handler, and Kind. In our case, we will be creating an action for Auth0. At the top navbar of the dashboard, click on Actions.

Hasura Auth0 action
Hasura Auth0 action

Then create an action. In action definition, add this code snippet:

type Query{
  auth0: auth0_profile
}

Since we are retrieving information from Auth0, we are using the Query type. In type definition, add this code snippet

type auth0_profile{
   id : String
   email : String
   picture : String
}

Next, we have to write a handler. The Handler has the logic that will be executed when we make a query to Auth0. We’ll get to that shortly. Save the Action. You should have something like this:

3. Write Action Handler

The major characteristics of the action handler are:

  •  It’s an HTTP webhook
  •  It’s a REST API endpoint
  •  Hasura receives payload from the Action through session_variables

Now let’s get to writing our first action handler.

Click on Codegen and choose nodejs-express in the select box.

Codegen
Codegen

Hasura generates some boilerplate code already for us. They also give us the option of hosting our HTTP server with glitch. Click on the Try with glitch. Go to src/server and paste this code snippet

// src/server

const express = require("express");
const bodyParser = require("body-parser");
const fetch = require('node-fetch');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(bodyParser.json());
const getProfileInfo = (user_id) => {
    const headers = {'Authorization': 'Bearer '+process.env.AUTH0_MANAGEMENT_API_TOKEN};
    console.log(headers);
    return fetch('https://' + process.env.AUTH0_DOMAIN + '/api/v2/users/'+user_id,{ headers: headers})
        .then(response => response.json())
}
app.post('/auth0', async (req, res) => {
  // get request input
  const { session_variables } = req.body;
  
  const user_id = session_variables['x-hasura-user-id'];
  // make a rest api call to auth0
  return getProfileInfo(user_id).then( function(resp) {
    console.log(resp);
    if (!resp) {
      return res.status(400).json({
        message: "error happened"
      })
    }
    return res.json({
      id: resp.user_id,
      email: resp.email,
      picture: resp.picture
    })
  });
});
app.listen(PORT);

Finally, add environment variables. Glitch has already created a .env file at the root of the project. Add the values for the following:

// .env
AUTH0_MANAGEMENT_API_TOKEN 
AUTH0_DOMAIN

Remember that you took notes of those variable values. When you’re done with that, click on the share button and copy the Live site link

 Share Glitch Project

Share Glitch ProjectNow our handler action URI will be https://hausraa.glitch.me/auth0. Go back to Hasura and add replace the Handler with your handler url and SAVE. Ensure you save.

Hasura Action Header
Hasura Action Header

4. Test the GraphQL API

At this point, we are going to test our GraphQL API with Hasura GraphiQL. Let’s first add the JWT token we generated from our Auth0 API as an Authorization header. On your Hasura dashboard, click the GraphiQL tab and add Authorization as a key, then paste the value like this Bearer eyJh… (the JWT token you took note of earlier). You should have something like this:

Add Authorization header
Add Authorization header

Now it’s time to test. Deselect the x-hasura-admin-secret Note: Always open the glitch server to be sure it’s not sleeping. Awesome. Run this query:

query MyQuery {
  auth0 {
    email
    id
    picture
  }
}

You should get this:

 Auth0 user profile
Auth0 user profile

Congratulations!!

Conclusion

They are 4 methods of handling authentication with Hasura. They are:

  • JWT-based Authentication
  • Admin Secret-based Authentication
  • Webhook-based Authentication and
  • Unauthenticated Access

In this article, we explored the JWT based Authentication method by using Auth0 as auth provider. We also made use of most of the core features of both platforms, most especially, Hasura Actions. Hasura Actions enables you to extend the Hasura GraphQL Schema and write your custom business logic for your GraphQL APIs.

Leave a Reply