Routing-controllers: question: is it possible to implement passport into the authorization features?

Created on 16 Aug 2017  路  7Comments  路  Source: typestack/routing-controllers

Im currently working with NodeJS Express in typescript and I am stuck with my rest api where im trying to implement passport-ldap and passport-jwt. (ldap for the login and after that i only want to authorize with jwt tokens) The problem is i have no idea how to set up the authorizationChecker with passport.authenticate().
So my question would be is it even possible, if yes then how?

question

Most helpful comment

@Yaojian - I had the same question, here's how I did it. Note that I'm using jwt authentication but you can just replace that with whatever your strategy is.

import * as express from 'express';
import * as passport from 'passport';
import { ExpressMiddlewareInterface, UnauthorizedError } from 'routing-controllers';
import { Logger, ILoggerInterface } from '../../decorators/logger';

export class JWTMiddleware implements ExpressMiddlewareInterface {
  constructor(
    @Logger(__filename) private log: ILoggerInterface
  ) { }

  // tslint:disable-next-line
  authenticate = (callback) => passport.authenticate('jwt', { session: false }, callback);

  use(req: express.Request, res: express.Response, next: express.NextFunction): Promise<passport.Authenticator> {
    return this.authenticate((err, user, info) => {
      if (err || !user) {
        this.log.info('Unauthorized access');
        this.log.info(info);
        return next(new UnauthorizedError(info));
      }

      req.user = user;
      return next();
    })(req, res, next);
  }
}

The reason why I wrapped passport.authenticate was so I could easily mock it in my unit test:

  it('should assign user if token is valid', () => {
    middleware.authenticate = jest.fn((callback) => {
      return () => {
        const err = undefined;
        const user = 'user';
        const info = undefined;
        return callback(err, user, info);
      };
    });

    const req = new MockExpressRequest({
      headers: {
        Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJuYy5zZXJ2aWNlIiwiaWF0IjoxNTIwOTY1ODQ1LCJleHAiOjE1MjA5NjY0NDUsInR5cGUiOiJJTlRFUk5BTCIsInVzZXIiOnsic3ViIjoiZGV2LnVzZXIifSwiaWQiOiIxNTJDNjZBMS1EOTRBLTQ5QjItQUVGQi03QjE3QTlEQkFERjUifQ.yRJLtPWJVXLCNcG33hstp_5lMFFyscOSDEZIEKuLD38'
      }
    });

    const next = (err) => {
      expect(err).toBeUndefined();
      expect(req.user).toEqual('user');
    };

    middleware.use(req, res, next);
  });

All 7 comments

Just mount passport.authenticate() as the normal global midddleware (or per controller/action with @UseBefore) and check the action.request.user property (equivalent of req.user in express) inside authorizationChecker with your auth&roles logic.

Passport.js is designed to work well in traditional express apps, however in routing-controllers we have features like authorizationChecker built-in so you might not need this at all 馃槈

Thank you, @Usebefore works perfectly with it

@Funnybanny Hi, could you give an example on the middleware for passport authentication? Thanks very much.

@Yaojian - I had the same question, here's how I did it. Note that I'm using jwt authentication but you can just replace that with whatever your strategy is.

import * as express from 'express';
import * as passport from 'passport';
import { ExpressMiddlewareInterface, UnauthorizedError } from 'routing-controllers';
import { Logger, ILoggerInterface } from '../../decorators/logger';

export class JWTMiddleware implements ExpressMiddlewareInterface {
  constructor(
    @Logger(__filename) private log: ILoggerInterface
  ) { }

  // tslint:disable-next-line
  authenticate = (callback) => passport.authenticate('jwt', { session: false }, callback);

  use(req: express.Request, res: express.Response, next: express.NextFunction): Promise<passport.Authenticator> {
    return this.authenticate((err, user, info) => {
      if (err || !user) {
        this.log.info('Unauthorized access');
        this.log.info(info);
        return next(new UnauthorizedError(info));
      }

      req.user = user;
      return next();
    })(req, res, next);
  }
}

The reason why I wrapped passport.authenticate was so I could easily mock it in my unit test:

  it('should assign user if token is valid', () => {
    middleware.authenticate = jest.fn((callback) => {
      return () => {
        const err = undefined;
        const user = 'user';
        const info = undefined;
        return callback(err, user, info);
      };
    });

    const req = new MockExpressRequest({
      headers: {
        Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJuYy5zZXJ2aWNlIiwiaWF0IjoxNTIwOTY1ODQ1LCJleHAiOjE1MjA5NjY0NDUsInR5cGUiOiJJTlRFUk5BTCIsInVzZXIiOnsic3ViIjoiZGV2LnVzZXIifSwiaWQiOiIxNTJDNjZBMS1EOTRBLTQ5QjItQUVGQi03QjE3QTlEQkFERjUifQ.yRJLtPWJVXLCNcG33hstp_5lMFFyscOSDEZIEKuLD38'
      }
    });

    const next = (err) => {
      expect(err).toBeUndefined();
      expect(req.user).toEqual('user');
    };

    middleware.use(req, res, next);
  });

Just mount passport.authenticate() as the normal global midddleware (or per controller/action with @UseBefore) and check the action.request.user property (equivalent of req.user in express) inside authorizationChecker with your auth&roles logic.

Passport.js is designed to work well in traditional express apps, however in routing-controllers we have features like authorizationChecker built-in so you might not need this at all 馃槈

Hi @19majkel94, thank you for the information. What about support for OAuth providers, like facebook / google?

@gruckion Hi. OAuth using google

export class Authenticate implements ExpressMiddlewareInterface {
  use(req: Request, res: Response, next?: (err?: any) => any): any {
    passport.authenticate("google", { scope: ["https://www.googleapis.com/auth/userinfo.email"] })(req, res, next);
  }
}

export class GoogleAuthentication implements ExpressMiddlewareInterface {
  authenticate = (callback) => passport.authenticate("google", { failureRedirect: "/login", session: false }, callback);

  use(req: Request, res: Response, next?: (err?: any) => any): any {
    return this.authenticate((err, user, info) => {
      if (err || !user) {
        return next(new UnauthorizedError(info));
      }

      req.user = user;
      return next();
    })(req, res, next);
  }
}

@Controller("/auth")
export class AuthController {
  @Get("/google")
  @UseBefore(Authenticate)
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  login() {}

  @Get("/google/redirect")
  @UseBefore(GoogleAuthentication)
  authenticate(@Req() req: Request, @Res() res: Response) {
    const token = "asda123sasd";
    res.redirect(`/?token=${token}`);
  }
}

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

iangregsondev picture iangregsondev  路  3Comments

humbertowoody picture humbertowoody  路  4Comments

GBeushausen picture GBeushausen  路  5Comments

posabsolute picture posabsolute  路  4Comments

AleskiWeb picture AleskiWeb  路  4Comments