BT

Facilitating the spread of knowledge and innovation in professional software development

Contribute

Topics

Choose your language

InfoQ Homepage Articles Bootstrapping the Authentication Layer and Server with Auth0.js and Hasura

Bootstrapping the Authentication Layer and Server with Auth0.js and Hasura

Bookmarks

Key Takeaways

  • Auth0 is a secure authentication platform that's easy to set up and provides features like SSO integration, cloud functions, and user management.
  • Hasura is a GraphQL engine for PostgreSQL that provides tools for rapid development. Hasura has both on-premise and cloud solutions.
  • Using Hasura and Auth0, you can bootstrap your authentication layer and save yourself days, if not weeks, of development time. 
  • You can use the Auth0 auth pipeline rules to automatically create JWT tokens with a custom payload to power Hasura's access control rules.
  • Hasura provides tools for setting up complex access control rules such as table relationships, session variables, and operators such as `_and`, `_or`, and `_not`.

When you're trying to prototype an MVP for your app and want to start iterating quickly, the upfront cost of setting up authentication can be a massive roadblock. The authentication layer requires significant work, and you must always be on the lookout for security vulnerabilities.

So it is logical to use third-party solutions to help yourself as much as possible.Today there's an abundance of cloud and on-premise services to help you reduce your development time.

Additionally, you can save effort by setting up a GraphQL server. For any application, one of the main reasons for a server, at least in the beginning, is to provide secure communication between your database and client.

With GraphQL, you no longer need to construct custom API endpoints to fetch data. Your client gets the flexibility to write GraphQL queries to fetch and format data however you wish, without any changes on the server. More direct access to data does not mean less security. With GraphQL you still can and should implement a robust and modular security layer.

Let's cover bootstrapping an authentication system and server using Auth0.js and Hasura. Using these tools, you can save yourself days, if not weeks, of development time and offload most of the complexity around setting up a secure and granular data access system.

We will set up a simple application with authentication powered by Auth0 and Hasura GraphQL as a server. Our front-end framework of choice is React, but the lessons of this tutorial equally apply Angular, Vue, or any other modern framework.

What is Auth0?

Auth0 is an authorization and authentication platform as a service. Auth0 helps you maintain and track user sessions, set up secure communication between your client and a server, and securely store sensitive user information on their platform. Auth0 solves some of the main pain points of dealing with authentication.

Other notable features of Auth0 include SSO integrations, automated authentication pipelines, and a customizable authentication interface

Reasons to use Auth0

Auth0 is not the only authentication service on the market. Another well-known one is Okta. Both Okta and Auth0 are great options but have slightly different target audiences and use cases.

Okta is focused more on the enterprise. One of its main features is allowing users in your company to sign into all their other company apps with ease.

Auth0, on the other hand, focuses more on supporting various SSO providers and serverless automation.

As a side note, Okta recently acquired Auth0, so it will be interesting to see how that will affect the features of these two products.

A third solid option for an authentication provider is AWS Cognito. At the time of writing, Cognito provides a generous 50,000 monthly active users with their free tier. Like the other providers, Cognito allows bootstrapping authentication interface, setting up SSO providers, and running automated functions using AWS Lambda.

The main benefit of Cognito is that you can set it up as an access control system for other AWS resources that your app consumes.

The biggest drawback of Cognito is the lack of support when it comes to customizing authentication flows and poor documentation. Both Okta and Auth0 have much more mature and user-friendly documentation.

For our tutorial, we will use Auth0 as it has a free plan for up to 7,000 users that requires no credit card, but Okta and Cognito might be other viable options for you.

What is Hasura?

Hasura is a GraphQL engine for PostgreSQL databases. Hasura is also not the only available GraphQL engine. There are other solutions like Postgraphile and Prisma. However, after trying a few of them, I've come to appreciate Hasura for several reasons:

  • Hasura is designed for client-facing applications and is one of the simplest solutions to set up. With Hasura, you get a production-level GraphQL server out-of-the-box that’s performant and has a built-in caching system.
  • Powerful authentication engine that’s based on the RLS (Row Level Security) that allows building granular and complex permission systems.
  • You can host Hasura on-premise using their Docker image, but you can also set up a working GraphQL server in a matter of minutes using Hasura cloud. This option is perfect for scaffolding your app and is the one we will use today.
  • Hasura's dashboard is powerful and user-friendly. You can write and test your GraphQL queries, manage your database schema, add custom resolvers and create subscriptions, all from one place.

There are, however, a few drawbacks to take into account when working with Hasura:

  • Not all databases are supported. Currently, Hasura works with PostgreSQL, Microsoft SQL Server, AWS Aurora, and Google's BigQUery. The team is also working on supporting other database engines such as Oracle, MongoDB, MySQL, and Elastic.
  • Hasura doesn't support aggregating data from multiple GraphQL nodes. It is something that their team is actively working on, but for right now, you can only run queries on a single GraphQL server instance.

All in all, Hasura is one of the best solutions for quickly setting up a GraphQL engine for your client-facing application.

Tutorial

Now that we have introduced both Auth0 and Hasura, let's get started with our tutorial! We will build a simple React application that uses Auth0's React SDK to communicate securely with our Hasura GraphQL server.

React application

Let's start by setting up our React app. Here's the repo of the project. To follow along, you will need to install create-react-app.

First, we initialize the project using the `npx create-react-app my-app` command. If you don’t have npx, you can easily install it globally by running the `npm install -g npx` command.

Next, we will need to install `react-router-dom` and Auth0 React SDK for setting up routes within a React web app and adding authentication code:

`yarn add react-router-dom @auth0/auth0-react`

Before moving further, we should set up our Auth0 and Hasura projects. Let's start with Auth0.

Auth0 project

Navigate to `manage.auth0.com` and log in to their dashboard. From there, create a new application:

Once you create the application, take a note of the following information:

  • Domain
  • Client id

Auth0 also provides you with a client secret. The client secret is a private key that you can use on your server to make requests to Auth0 API, but we won't be needing it for this tutorial.

Next, we need to set up application URLs for Auth0. We need to set up three URLs:

  • Allowed Callback URLs. The list of URLs to redirect the user back from the authentication process. In our case, it'll be the root URL of our server.
  • Allowed Logout URLs. URLs to return to after logging out the user. This will also be the root URL.
  • Allowed Web origins. List of URLs that can use your Auth0 project. You need to add your app's URL to this list.

Make sure to click the "Save" button for the changes to propagate.

Now let's hop back to our React app and finish setting up Auth0 SDK. To do that, we need to add Auth0Provider at the root of your application which will allow us to use the SDK throughout the app:

import './App.css';
import Routes from './routes';
import { Auth0Provider } from '@auth0/auth0-react'
import ApolloGraphqlProvider from './ApolloProvider';

const redirectURL = window.location.origin;

function App() {
  return (
    <div className="App">
      <Auth0Provider domain="YOUR_DOMAIN"
        clientId="YOUR_CLIENT_ID"
        redirectUri={redirectURL}>
        <ApolloGraphqlProvider>
          <Routes />
        </ApolloGraphqlProvider>
      </Auth0Provider>
    </div>
  );
}

export default App;

Now we can use Auth0’s functionality using the `useAuth0` hook. There are many things we can do with it. For example, we can access user information stored in Auth0 from any component:

import { useAuth0 } from '@auth0/auth0-react'
import React from 'react'

export default function Private() {
    const { user } = useAuth0()

    return (
        <div>
            <h1>Private</h1>
            <p>{user.email}</p>
            <p>{user.name}</p>
        </div>
    )

Some of the other features you’re most likely to use are:

  • `getAccessTokenSilently` - allows generating access tokens that you can use to communicate safely with your server. As a side note: Auth0 also has SDKs for server-side applications that help with verifying generated access tokens. 
  • `logout` -  `logout` logs the user out of the application by clearing the current session and redirecting to the URI you specified when setting up the `Auth0Provider`.
  • `isAuthenticated` - a boolean that indicates whether the user is currently logged in. Using `isAuthenticated`, you can decide whether to allow the user to see certain routes or components.

Hasura project

Next, let's move on to setting up our Hasura project.

To follow along with this tutorial, you will need a running PostgreSQL database instance. 

As I mentioned, to keep things simple, we will use Hasura cloud.

Navigate to Hasura and click on the `Get Started` button:

Once you log in, click on `Create a project`, pick a free tier and provide a name for your project.

Finally, click `Launch Console` to get redirected to your project's console. 

Hasura’s dashboard is pretty powerful, you can manage your database and access-control system from it, among many other things.

Let's connect our database. Click on the `Data` tab, and you will see a connection form. You can choose to provide a PostgreSQL connection string or input each database parameter separately.

Once you have connected your database, you can start adding schemas and tables as well as writing GraphQL queries. Hasura provides a graphiql IDE interface that will be familiar if you've worked with GraphQL before.

We will circle back to setting up table permissions, but first, we need to set up secure JWT token generation.

JWT with Auth0 cloud functions

The next step is setting up the JWT token system for sending secure queries to Hasura. We will need to provide Hasura with a payload it understands and uses to enforce table security rules.

Auth0 generate JTW tokens for us, but they don't have the payload Hasura needs. To solve that, we can use Auth0's pipeline rules. We will create a custom rule that will add the necessary payload data when JWT token is generated. 

Navigate to the rules console from Auth0's dashboard under the `Pipeline` menu.

From there, create a new rule, you can call it whatever you want. Input the following 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.email
    };
  callback(null, user, context);

}

Here we're simply adding `x-hasura-user-id`, `x-hasura-default-role` and `x-hasura-allowed-roles`  fields that Hasura understands and which we will use later when setting up our database access control system.

Configuring Hasura to use JWT token

The next thing we need to do is configure Hasura to verify our JWT tokens.

Each Auth0 application has a domain and we will need it to set up our configuration. Navigate to the settings page of your Auth0 application and copy your domain. Next, visit https://hasura.io/jwt-config/ and use the form Hasura provides for generating the JWT config objects. The config object will contain a certificate string that Hasura will use on its back-end to verify the token’s signature. All you need to do is specify your Auth0 domain, and the tool will create the config object for you.

Now you need to set the config object you created as an environment variable for your Hasura instance. Navigate to your Hasura project's settings page and click on `New env var` from the `Env vars` tab. Use `HASURA_GRAPHQL_JWT_SECRET` and save your JSON config as a variable.

At this point, your Hasura engine should be fully configured to work with JWT tokens generated by Auth0.

Using JWT tokens in React

You’re free to use any GraphQL client you prefer, just be aware of how Auth0 creates JWT tokens. The `getAccessTokenSilently` method Auth0 provides for generating tokens is asynchronous. This might be a problem for GraphQL clients that typically instantiate synchronously on the page load, for example, the Apollo client.

There is, however, a workaround you can implement with Apollo client using setContext:

import {
  ApolloClient,
  InMemoryCache,
  createHttpLink,
  ApolloProvider,
} from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
import { useAuth0 } from "@auth0/auth0-react";
import fetch from "isomorphic-fetch";

const API_URL = "https://YOUR_GRAPHQL_ENDPOINT/v1/graphql";


export default function ApolloGraphqlProvider({
  children,
}) {
  const { getAccessTokenSilently } = useAuth0();

  const authLink = setContext(async (_, { headers }) => {
    let accessToken = null;
    try {
      accessToken = await getAccessTokenSilently();
    } catch (e) {
      console.log({ e });
    }

    headers = { ...headers };
    if (accessToken) {
      headers.authorization = `Bearer ${accessToken}`;
    }

    return { headers };
  });

  const httpLink = createHttpLink({
    fetch,
    uri: API_URL,
  });

  const client = new ApolloClient({
    link: authLink.concat(httpLink),
    cache: new InMemoryCache(),
  });

  return <ApolloProvider client={client}>{children}</ApolloProvider>;
}

 

`setContext` is made specifically for situations where you need to asynchronously generate or lookup authentication data, such as tokens. It accepts a function that can either return an object or a promise returning an object. The object returned is used as a context for your GraphQL queries.

Another thing to note in the code above is our use of isomorphic-fetch.  isomorphic-fetch is a library that ensures consistent implementation of the fetch API across all the browsers (and within Node.js) and guarantees the same behavior for our GraphQL client.

Now we’re ready to start sending queries with our GraphQL client.

Hasura Access control rules

Now that we have our JWT token communicating safely with Hasura, it's time to set up the access control system for our database.

Under the hood, access control rules are enforced using PostgreSQL row-level security. When you send GraphQL queries, Hasura transforms them into SQL statements and applies the access control rules you specified.

Granularity

With Hasura, there are three levels of granularity you can control:

  • Role
  • Table
  • Action

You can configure role permissions for each table. Click on any table and select the Permissions tab:

The role of the current user is derived from the `x-hasura-default-role` payload field we set up earlier. By default, your GraphQL engine generates the admin role, but you can always add more custom roles.

On top of that, you can specify which actions the role can perform on a given table. Notice how we have four distinct columns for each role: insert, update, read and delete. Controlling permissions for each action type allows you to get as granular with your role permissions as you want.

Row & Column permissions

For each of the four actions, you can specify additional permissions on row and column levels. Again, Hasura will use the token payload to enforce this kind of access.

Row-level permissions are boolean expressions that run each time the table is accessed to determine which rows are allowed. In the screenshot above, the boolean expression states that we can only insert rows with `user_id` equal to the value of `X-Hasura-User-Id` JWT payload field. Under the hood, Hasura translates the payload fields into session variables. We will cover using session variables in more detail shortly.

We can also specify which columns the given role can access by toggling checkboxes in the `Column Insert Permissions` section.

I hope this shows you how powerful Hasura's access control dashboard is. If you've ever set up granular database permissions manually or using command-line tools, you know how tedious this process can get.

Guide to setting up access control rules

Now let's cover in more detail the different scenarios you might run into when trying to set up access control rules for your application.

Using session variables

If you recall, payload fields from our JWT token get translated into session variables that you can use to build dynamic access rules. Let's revisit our earlier example:

In this case, we're using the JWT payload field `X-Hasura-User-Id`. Hasura translates that field into a session variable with the same name. We use the variable to make sure each user can only see the rows they created.

Using table relationships and nested objects

We can also build access rules using table relationships that are based on foreign keys.

Let’s look at the example. Say we have `message` and ` user` tables, and they have a table relationship through their foreign keys. We also have a `Team-Id` payload field that we want to use to get all the messages that belong to your teammates. We can create an access rule on the `message` table that uses the `team_id` column of the related `user` table. Here's what it would look like:

Using this access rule, you can read the messages of all of the users whose `team_id` column matches the `Team-Id` field of your payload. In other words, all of the messages that belong to your teammates.

Setting up more complex permissions with _and, _or and _not

You can build even more complex rules using `_and`, `_or` and `_not` operators. Using these operators you can combine multiple boolean expressions to create a unique permission logic to meet the needs of any application.

Let's say you want to let the users read only the messages they create that also have a type of "free". We can use the `_and` operator to combine these two boolean expressions like so:

Operators for combining multiple boolean expressions make it possible to support the logic of the highest complexity.

Conclusion

To summarize, we talked about bootstrapping authentication systems in your application using the Hasura GraphQL engine and Auth0.js. We covered the pros and cons of each tool and how to set them up in your project. 

We also covered how to use Hasura's access control rules to build a granular, role-based access system. We explored advanced use cases that involve session variables, table relations, and combining multiple boolean expressions using operators.

Hasura's GraphQL engine is an open-source project available under the "Apache 2" license. To learn more about contributing, read their contribution guidelines.

About the Author

Iskander Samatov is currently a senior software engineer at HubSpot. He was previously involved with several startups in legal tech such as Joinder, building communication platforms and streamlining processes for professionals in the legal industry. Iskander has developed a number of mobile and web apps, used by thousands of people, like Planly. Iskander frequently blogs at https://isamatov.com.

Rate this Article

Adoption
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Community comments

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

BT

Is your profile up-to-date? Please take a moment to review and update.

Note: If updating/changing your email, a validation request will be sent

Company name:
Company role:
Company size:
Country/Zone:
State/Province/Region:
You will be sent an email to validate the new email address. This pop-up will close itself in a few moments.