将Auth0集成为Express.js路由控制器AuthorizationChecker中的中间件

问题描述

我很难将Auth0's Node (Express) API quickstart转换为中间件变体。特别是使用Typestack的路由控制器库,并希望使用其@Authorized decorator集成Auth0。

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]
});

status-controller.ts 中,我用@Authorized及其中的角色装饰了一个操作:

@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';
    }
}

我的问题:如何配置authorizationChecker函数,以使其既验证令牌又尊重角色(上例中的'super-duper-trooper')?

我曾尝试将其添加为常规的Express.js中间件(app.use(MyMiddleware)),但由authorizationChecker函数取代。

解决方法

我设法让authorizationChecker用于Express.js中的路由控制器。

我通过合并jsonwebtokenjwks-rsa库来做到这一点。

请参阅以下验证JWT的auth函数:

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,{},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;
}

然后可以在authorizationToken函数中使用此函数,如下所示:

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]
});

配置完成后,可以像执行操作一样用@Authorize()@Authorize('role')装饰控制器中的动作。这将在每次执行该操作之前触发authorizationChecker

注意:整个getPublicKey部分从端点检索公共密钥,也可以替换为仅在代码中或某个地方的设置中使用公共密钥。这样,您也无需手动创建promise即可等待JWT验证。但是,我认为按需检索公钥是更优雅的解决方案。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...