1

I'm having difficulty in translating Auth0's Node (Express) API quickstart to a middleware variant. In particular using TypeStack's routing-controllers library and want to integrate Auth0 using their @Authorized decorator.

In app.ts:

const app = createExpressServer({

    authorizationChecker: async (action: Action, roles: string[]) => {
        const token = action.request.headers['authorization'];

        // QUESTION: How to validate the bearer token here so it returns true/false
        // + also respect the role ('super-duper-trooper')?

        return true/false;   // <----- this
        
    },

    controllers: [StatusController]
});

In status-controller.ts I decorate an operation with @Authorized + a role in it:

@Controller()
export class StatusController {

    @Get('/status')
    getAll() {
       return 'OK, anonymously accessible';
    }

    @Authorized('super-duper-trooper')   // <----- that
    @Get('/status/:id')
    getOne(@Param('id') id: string) {
       return 'NOT OK';
    }
}

My question: How to configure the authorizationChecker function so that it both validates the token and respects to role ('super-duper-trooper' from the example above)?

N.B. I've tried adding it as regular Express.js middleware (app.use(MyMiddleware)), but that is superseeded by the authorizationChecker function.

Juliën
  • 9,047
  • 7
  • 49
  • 80

1 Answers1

2

I have managed to get the authorizationChecker to work for routing controllers in Express.js.

I've done this by incorporating the jsonwebtoken and jwks-rsa libraries.

See the following auth function that verifies the JWT it is given:

import jwt from 'jsonwebtoken';
import jwksRsa from 'jwks-rsa';

export async function AuthMiddleware(token: string, roles: string[]): Promise<boolean> {
    if (!token) return false;

    // Extracts the bearer token from the request headers
    const bearerToken = token.split(' ')[1];

    // Set up a JWKS client that retrieves the public key from Auth0, this public key will be used to challenge the
    // bearer token against.
    const client = jwksRsa({
        jwksUri: 'https://your_jwks_uri.com/jwks.json' // For example, using Auth0 you can find this in Auth0 Applications -> Advanced Settings -> Endpoints. This should look something like this: https://yourtenant.eu.auth0.com/.well-known/jwks.json
    });
    const getPublicKey = (header: any, callback: any) => {
        client.getSigningKey(header.kid, (err, key) => {
            const signingKey = key.getPublicKey();
            callback(null, signingKey);
        });
    }

    // As jwt.verify cannot be awaited, we construct a promise that we will resolve once the JWT verification has
    // finished. This way, we can simulate awaiting of the JWT verification.
    let jwtVerifyPromiseResolver: (tokenValid: boolean) => void;
    const jwtVerifyPromise = new Promise<boolean>(resolve => {
        jwtVerifyPromiseResolver = resolve;
    });

    const tokenNamespace = 'your_namespace'; // The namespace you have added to the roles in your auth token in an Auth0 rule

    jwt.verify(bearerToken, getPublicKey, {}, (err, decodedJwt: any) => {
        let jwtValid: boolean = false;

        if (err)
            jwtValid = false;
        else {
            // When the requested endpoint requires roles, check if the decoded JWT contains those roles
            if (roles && roles.length > 0) {
                const userRoles = decodedJwt[`${tokenNamespace}roles`];

                if (userRoles)
                    // Token is valid if all roles for request are present in the user's roles
                    jwtValid = roles.every((role) => userRoles.includes(role));
                else
                    // Token does not contain roles, mark token as invalid
                    jwtValid = false;
            }

            jwtValid = true;
        }

        jwtVerifyPromiseResolver(
            jwtValid
        );
    });

    return jwtVerifyPromise;
}

This function can then be used in the authorizationToken function, like so:

const app = createExpressServer({
    authorizationChecker: async (action: Action, roles: string[]) => {
        const authorizationToken = action.request.headers['authorization'];

        // Wait for JWT verification to complete, returning whether the token is valid or not
        return await AuthMiddleware(authorizationToken, roles);
    },

    controllers: [StatusController]
});

After having this configured, you can decorate the actions in your controllers with @Authorize() or @Authorize('role') like you're already doing. This will trigger the authorizationChecker before every request to the action.

Note: the whole getPublicKey part that retrieves the public key from an endpoint can also be replaced by just having your public key in your code or in a setting somewhere. This way, you also don't need to create the promise manually to await the JWT verification. However, I thought retrieving the public key on-demand was the more elegant solution.

Bryandh
  • 539
  • 1
  • 5
  • 22