'express-oauth2-jwt-bearer' requiredScopes Express middleware and missing user assigned permissions within the scope

I have followed the boilerplate documentations for configuring a Vue SPA and also configuring the associated backend via Express using the provided middleware handlers exposed via the ‘express-oauth2-jwt-bearer’ package.

All of this works great!

However, I did run into one issue which I have found some prior topics that kinda hit upon the subject, but I wanted to re-post to try and get another response.

Long story short:

If you have an Express route that has for example the auth and the requiredScopes middleware handlers configured… I want to verify the incoming JWT but also then make sure the user has specific assigned permissions…

e.g:

import { Request, Response, NextFunction } from 'express'
import { auth, requiredScopes } from 'express-oauth2-jwt-bearer'

router.get(
  '/private-scoped',
  auth,
  requiredScopes(['read:users', 'write:users']),
  (_req: Request, res: Response) => {
    res.json({
      message: 'Hello from a private SCOPED endpoint!.',
    })
  },
)

When the middleware hits the requiredScopes, I was looking at the stack trace , it will check that the scope property on token payload has the permissions I have passed into the requiredScopes.

Long story short: How do I make the permissions I have assigned directly to the user via the Auth0 web interface get included within the scope of the token payload?

I was able to solve this by enabling on my Auth0 custom API identifier settings the “Enable RBAC” + “Add Permissions to Access Token”.

This then adds the “permissions” property with the array of permissions that the user has.
I then wrote my own middleware to check the “permissions”. This works great, but I just want to make sure I’m not missing something. I wanted to follow your “best practices” per your knowledge base / tutorials.

Here is my implementation of a requiredPermissions middleware

import { Request, Response, NextFunction } from 'express'

export const requiredPermissions =
  (permission: string | string[]) =>
  (req: Request, res: Response, next: NextFunction) => {
    const userPermissions = req.auth.payload.permissions as string[]
    if (!permission || !userPermissions) {
      return res.sendStatus(401)
    }
    if (typeof permission === 'string') {
      return userPermissions.includes(permission) ? next() : res.sendStatus(401)
    }
    if (Array.isArray(permission)) {
      return permission.every(p => userPermissions.includes(p))
        ? next()
        : res.sendStatus(401)
    }
  }

Hey there @jzbg welcome to the community!

This is great to hear!

Your requiredPermissions is fine to use, but the library does provide a claimIncludes function that can be used to check the permissions claim of access tokens - For example:

const checkJwt = auth();

app.get('/api/external/protected', checkJwt, claimIncludes('permissions', 'required_permission', 'required_permission' ), (req, res) => {
  res.json({ message: `Hello ${req.auth.payload.sub} - Permissions: ${req.auth.payload.permissions}` });
});
1 Like

@tyf

Thanks so much for your prompt reply.

Yes, that library method will work great. I prefer to utilize the library provided method instead of rolling my own.

For anyone who happens to stumble upon this post:

Here is an exported middleware Express handler that utilizes the underlying claimIncludes library method exposed in the express-oauth2-jwt-bearer package.

export const authRequiredPermissions = (permission: string | string[]) => {
  if (typeof permission === 'string') {
    permission = [permission]
  }
  return claimIncludes('permissions', ...permission)
}

Make sure to “Enable RBAC” + “Add Permissions to Access Token” on your API Identifier.

Finally, you can utilize the actual middleware like this…


router.get(
  '/private-scoped',
  authCheckJwt,
  authRequiredPermissions('read:users'), // For multiple permissions pass array of permissions.
  (_req: Request, res: Response) => {
    res.json({
      message: 'Hello from a private SCOPED endpoint!.',
    })
  },
)
1 Like

No problem, happy to help! Great to know that will work for you and thanks a bunch for the additional details on your setup, always appreciated :slight_smile:

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.