# Auth0 for authentication

We rely on Auth0 for authentication. The tenant used for produciton is japan-rabbit, while the staging one is japan-rabbit-staging (shared with other projects).

The login page is just a redirect to Auth0's Universal Login page.

The /login route handles Auth0 callback and logout redirection.

Apart from configuration in Auth0 dashboard itself, we have environment variables to specify the tenant and callbacks to Auth0. See environment docs for more information.

# Authorization

Apart from checking ownership of resources, authorization will be based on roles. Every user has a role assigned that will change its view and permissions in the app.

These roles are defined as enums in the database and assigned to the User type. Authorization will be asserted by Hasura.

To make this work, the access token generated by Hasura needs to include User's ID and role.

# Extending Access Token Information

Auth0 allows to customize both id and access tokens. The idea is to provide user's id and role inside the access token to prevent extra DB requests later on. In order to do this, Auth0 must call Hasura to create a new user or get its id and role.

The following rule makes a query to our application and modifies the access token with the returned information:

function (user, context, callback) {
  const SERVER_ENDPOINT =  "https://<hasura-server-url>/v1/graphql";
  const SECRET = '<admin secret>';
  const namespace = "https://hasura.io/jwt/claims";

  const { user_id: authId, nickname: name, email } = user;

  const payload = {
    query:`
      mutation UpsertUser($authId:String!, $email:String!, $name:String!){
        insert_users_one(object: {
          auth_id: $authId, email: $email,
          name: $name, role: CUSTOMER, last_seen: "now()"
        }, on_conflict: {
          constraint: users_email_key, update_columns: [last_seen]
        }) { id role }
      }
      `,
    variables:{ authId, email, name }
  };

  const got = require('got@10.7.0');
  got.post(SERVER_ENDPOINT, {
    headers: {'x-hasura-admin-secret': SECRET},
    json: payload,
    responseType: 'json',
    retry: { methods: ['POST'] }
  })
    .then(({ body }) => {
    const { id , role } = body.data.insert_users_one;

    context.accessToken[namespace] =
      {
      'X-Hasura-Default-Role': role,
      'X-Hasura-Allowed-Roles': [role],
      'X-Hasura-User-Id': id
    };

    return callback(null, user, context);
  })
    .catch(error =>{
    console.log(error);
    return callback(new UnauthorizedError('syncUser:' + error.message));
  });

Last updated: 6/26/2020, 9:59:26 AM