Nest: Authorize decorator.

Created on 24 Nov 2017  路  20Comments  路  Source: nestjs/nest

I'm submitting a...


[ ] Regression 
[ ] Bug report
[x] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current behavior

export class AuthModule implements NestModule {
  public configure(consumer: MiddlewaresConsumer) {
    consumer
      .apply(passport.authenticate('jwt', { session: false }))
      .forRoutes({ path: '/auth/authorized', method: RequestMethod.ALL });
  }
}

Expected behavior

Get routes to apply middleware by decorator.

export class AuthModule implements NestModule {
  public configure(consumer: MiddlewaresConsumer) {
    consumer
      .apply(passport.authenticate('jwt', { session: false }))
      .forRoutes()
      .with(Authorize);
  }
}
import { Controller, Post, HttpStatus, HttpCode, Get } from '@nestjs/common';
import { AuthService } from './auth.service';

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post('token')
  @HttpCode(HttpStatus.OK)
  public async getToken() {
    return await this.authService.createToken();
  }

  @Get()
  @Authorize()
  public async authorized() {
    console.log('Authorized route...');
  }
}

Minimal reproduction of the problem with instructions

N/A

What is the motivation / use case for changing the behavior?

this way it is much simpler to apply middleware to a collection of routes with a specific decorator.

Otherwise it is much more readable and easy to identify if the route needs authentication or not.

Environment


Nest version: 4.4.0


For Tooling issues:
- Node version: 8.9.1 
- Platform:  Windows

Others:

question 馃檶

Most helpful comment

@Guard()
export class AuthGuard implements CanActivate {
    async canActivate(dataOrRequest, context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
      const isAuthenticated = await new Promise<boolean>((resolve, reject) => {
        passport.authenticate('jwt', { session: false }, (args) => {
              if (args != 200) {
                return resolve(false);
              }
              return resolve(true);
          })(dataOrRequest.res.req, dataOrRequest.res, dataOrRequest.nex);
      });
      if (!isAuthenticated) {
        throw new HttpException('', HttpStatus.UNAUTHORIZED);
      }
      return true;
    }
}

All 20 comments

This can be achieved with the new decorator added here: https://github.com/nestjs/nest/pull/240

Simply make this decorator return a boolean of whether or not the request is authorized.

With this approach the request arrives in controller action.

If we use middleware we can stop request before to arrive in controller.

I don't know if I'm correct.

@nicolastakashi in this case you need to use guards

@zMotivat0r If I use guards I can't use passport.apply

@nicolastakashi why not? 馃檪 guards have an access to the request, which has a res property. Pass custom callback and use next() as a resolve function of the promisified callback.

@kamilmysliwiec I do that, looks like good. But the callback never is fired.

@Guard()
export class AuthGuard implements CanActivate {
    canActivate(dataOrRequest, context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
        return new Promise<boolean>((resolve, reject) => {
            passport.authenticate('jwt', function (args) {
                return resolve(true);
            })
        });
    }
}

@nicolastakashi passport.authenticate() returns a function that you have to call 馃檪 (it accepts req, res and next)

@kamilmysliwiec sorry budy.

I don't understand. Can you send me a example.

As far as I remember it should be sth like this:

passport.authenticate('jwt', function (args) {
    return resolve(true);
})(req, res, next);

I try to do this.

But the callback never is resolved.

So there's a missing (req, res, next); in what you have sent before.

@kamilmysliwiec It's works.

But I found some problems, If I don't pass token in header it keep working such as authorized user.

And callback params is always null.

and If a return false I just can get 403 and not a 401

Your custom callback accepts several params. Take a look at passport docs, there should be a err and info argument. :) In the Nest docs there's a solution how to change the default guard behaviour (just throw HttpException exception instead of returning false value)

I'm so sorry man.

I had not noticed the part where it talks to throw an httpexception

I will try it.

Thank you for now

@kamilmysliwiec

I wrote this code:

import { Guard, CanActivate, ExecutionContext, UnauthorizedException, HttpException, HttpStatus } from '@nestjs/common';
import { Observable } from 'rxjs/Observable';
import * as passport from 'passport';

@Guard()
export class AuthGuard implements CanActivate {
    async canActivate(dataOrRequest, context: ExecutionContext): Promise<boolean> {
        return new Promise<boolean>((resolve, reject) => {
            passport.authenticate('jwt', { session: false }, function (args) {
                if (args != 200) throw new UnauthorizedException();

                return resolve(true);
            })(dataOrRequest.res.req, dataOrRequest.res, dataOrRequest.nex);
        });
    }
}

Works like a charm.

So I create a Exception Filter, But I can't filter this and I get this message in console.

(node:8512) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): #<HttpException>
(node:8512) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Any Idea?

I think that comes from throw. Try using reject instead?

@wbhob rect does't work because promise is a a boolean.

@Guard()
export class AuthGuard implements CanActivate {
    async canActivate(dataOrRequest, context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
      const isAuthenticated = await new Promise<boolean>((resolve, reject) => {
        passport.authenticate('jwt', { session: false }, (args) => {
              if (args != 200) {
                return resolve(false);
              }
              return resolve(true);
          })(dataOrRequest.res.req, dataOrRequest.res, dataOrRequest.nex);
      });
      if (!isAuthenticated) {
        throw new HttpException('', HttpStatus.UNAUTHORIZED);
      }
      return true;
    }
}

all it took for me

   @Get('user')
    @UseGuards(PassportAuthGuard)
    public async user(@Req() req) {
        return req.user;
    }

@Injectable()
export class PassportAuthGuard implements CanActivate {
    canActivate(
        context: ExecutionContext,
    ): boolean | Promise<boolean> | Observable<boolean> {

        const req = context.switchToHttp().getRequest();

        if (!req.isAuthenticated()) {
            throw new UnauthorizedException();
        }

        return true;
    }
}

This thread 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