Search code examples
oauth-2.0jwtaws-api-gatewayauth0httpapi

Create different JWT authoriser in Auth0 and attach permissions based on the permitted roles


There are three kinds of users in my Auth0 tenant:

  1. Regular user (no role)
  2. Moderator user (assigned "Mod" role)
  3. Admin user (assigned "Admin" role)

I created an API in Auth0 and attached to the endpoints via JWT authoriser in the new AWS API Gateway HTTP API.

There is business logic that some endpoints allows only allows regular user and admin, and some allow Mod and Admin. E.g.:

  1. Endpoint 1: Allow regular user and Admin
  2. Endpoint 2: Allow Mod and Admin
  3. Endpoint 3: Allows Mod only

Currently, the authoriser allows any user in the user's database in Auth0, and I check the user's identity within the application via several Auth0's management API:

  1. /userInfo to make sure the token matches with the :user_id.
  2. /oauth/token to get Auth0 management API access token.
  3. /api/v2/users/:user_id to get the user profile.
  4. /api/v2/users/:user_id/roles to get the role.

I believe there should have a better way to handle the identity check. Is it possible to create multiple authoriser with a different role/permission scope (e.g. allow regular user and admin) and attach to the related endpoint accordingly?


Solution

  • I realised there is a claims object in event.requestContext.authorizer.claims from Lambda according to the AWS API Gateway Doc.

    1. Therefore, the extra /userInfo call is unnecessary.
    2. I added 2 Auth0 rules to merge the user role to user.app_metadata and the claims object.

    Code sample for #2:

    function assignRoleToAppMetadata (user, context, callback) {
      const ManagementClient = require('[email protected]').ManagementClient
      const management = new ManagementClient({
        domain: '{YOUR_ACCOUNT}.auth0.com',
        clientId: '{YOUR_NON_INTERACTIVE_CLIENT_ID}',
        clientSecret: '{YOUR_NON_INTERACTIVE_CLIENT_SECRET}'
      })
      const params = { id: user.user_id }
      management.getUserRoles(params)
        .then(roles => {
          user.app_metadata = user.app_metadata || {}
          user.app_metadata.roles = roles
          return auth0.users.updateAppMetadata(user.user_id, user.app_metadata)
        })
        .then(() => callback(null, user, context))
        .catch(err => {
          console.error(err.message)
          callback(null, user, context)
        })
    }
    

    In addition, the following rule attached the role info to /userInfo:

    function(user, context, callback) {
      const namespace = 'https://{YOUR_ACCOUNT}.auth0.com/'
      context.idToken[namespace + 'roles'] = user.app_metadata.roles
      callback(null, user, context)
    }