Search code examples
node.jsrestexpressinversifyjs

Inject HttpContext into InversifyJS middleware


I have the following controller.

@controller('/users')
class UsersController {
    @httpGet('/', authMiddleware({ role: 'ADMIN' }))
    public get() { ... }
}

I have implemented a custom AuthenticationProvider, which returns a principal containing details about the currently authenticated user, including the user's roles.

.... 
return new Principal({
  firstName: "John",
  lastName: "Smit",
  roles: ["ADMIN"]
});
...

This all works fine, but I am wondering how I can retrieve the principal from the authMiddleware which is used by the above GET route.

For now I have an ugly hack which uses internals of InversifyJS.

function authMiddlewareFactory() {
  return (config: { role: string }) => {
     return (
         req: express.Request,
         res: express.Response,
         next: express.NextFunction
     ): void => {
         const httpContext: interfaces.HttpContext = 
         Reflect.getMetadata(
             "inversify-express-utils:httpcontext",
             req
         );
         const principal: interfaces.Principal = httpContext.user;
         if (!principal.isInRole(config.role)) {
             res.sendStatus(HttpStatus.UNAUTHORIZED);
             return;
         }
         next();
     };
   };
 }

The custom authentication provider uses the authorization header to authenticate the user and returns a principal. I don't want to do this work again in the middleware, I just want to retrieve the principal.

This hack works, but I was wondering if someone knows a cleaner way of obtaining the HttpContext in this middleware.

I know you can access the HttpContext and thus the principal (user) if you extend from the BaseMiddleware, but then it's not clear to me how you pass configuration (parameters) to it, such as the desired role. Related to the following issue on InversifyJS.

https://github.com/inversify/InversifyJS/issues/673


Solution

  • This is not supported, but I can see why it is needed. We cannot pass the httpContext to the middleware as an argument because we want to keep the standard Express middleware compatible. This means that the only option is doing something like what you have done but ideally we should encapsulate it using some helper.

    We need to implement something like the following getHttpContext function:

    import * as express from "express";
    import { getHttpContext } from "inversify-express-utils";
    
    function authMiddlewareFactory() {
      return (config: { role: string }) => {
         return (
             req: express.Request,
             res: express.Response,
             next: express.NextFunction
         ): void => {
             const httpContext = getHttpContext(req);
             const principal: interfaces.Principal = httpContext.user;
             if (!principal.isInRole(config.role)) {
                 res.sendStatus(HttpStatus.UNAUTHORIZED);
                 return;
             }
             next();
         };
       };
     }
    

    Until this is implemented I don't see any problems with your implementation other than the information leakage of the inversify internals.