CountZero
CountZero

Reputation: 196

NestJS jwt-passport Authentication

I want to implement a distributed authentication library to use it on several projects. The library should implement JWT authentication method. The code is as follows:

jwt.strategy.ts

import {ExtractJwt, Strategy} from 'passport-jwt';
import {PassportStrategy} from '@nestjs/passport';
import {Injectable} from '@nestjs/common';
import {JwtPayload, User} from './interfaces';
import {ConfigService} from "./config.service";

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
  constructor(private readonly configService: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: configService.get('secretOrPrivateKey'),
    });
  }

  async validate(payload: JwtPayload): Promise<User> {
     return {
      uuid: payload.uuid,
      email: payload.email,
    }
  }
}

jwt.auth.module.ts:

import {Module, DynamicModule} from '@nestjs/common';
import {JwtModule} from '@nestjs/jwt';
import {JwtStrategy} from './jwt.strategy';
import {PassportModule} from '@nestjs/passport';
import {ConfigService} from "./config.service";
import {JwtOptions} from "./interfaces/jwt.options";

@Module({
})

export class JwtAuthModule {
  static forRoot(jwtOptions): DynamicModule {
    return {
      module: JwtAuthModule,
      imports: [
        // JwtModule.register(jwtOptions),
        // PassportModule.register({defaultStrategy: 'jwt'}),
      ],
      providers: [
        JwtStrategy,
        {
          provide: ConfigService,
          useValue: new ConfigService(jwtOptions),
        }
      ],
      exports: [ConfigService, JwtStrategy]
    };
  }
}

and I have imported this in my app.module.ts:

import { Module, NestModule, HttpModule } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { environment } from './environments';
import { AuthModule } from './auth/auth.module';
import { PermissionModule } from './permission/permission.module';
import {JwtAuthModule} from '@pe/nest-kit';
import {JwtModule} from '@nestjs/jwt';
import {PassportModule} from '@nestjs/passport';

@Module({
  imports: [
    JwtModule.register(environment.jwtOptions),
    PassportModule.register({defaultStrategy: 'jwt'}),
    JwtAuthModule.forRoot(environment.jwtOptions),
    HttpModule,
    AuthModule,
    PermissionModule,
    MongooseModule.forRoot(environment.mongodb),
  ],
})
export class ApplicationModule implements NestModule {
  configure() {
  }
}

however, each time I try to open project url, I'm getting an error:

[Nest] 27645 - 24.10.2018, 15:23:26 [ExceptionsHandler] Unknown authentication strategy "jwt" +4119ms Error: Unknown authentication strategy "jwt" at attempt (/home/user/workspace/permissions/node_modules/passport/lib/middleware/authenticate.js:187:37) at authenticate (/home/user/workspace/permissions/node_modules/passport/lib/middleware/authenticate.js:363:7) at Promise (/home/user/workspace/permissions/node_modules/@nestjs/passport/dist/auth.guard.js:83:3) at new Promise () at /home/user/workspace/permissions/node_modules/@nestjs/passport/dist/auth.guard.js:75:83 at MixinAuthGuard. (/home/user/workspace/permissions/node_modules/@nestjs/passport/dist/auth.guard.js:47:36) at Generator.next () at /home/user/workspace/permissions/node_modules/@nestjs/passport/dist/auth.guard.js:19:71 at new Promise () at __awaiter (/home/user/workspace/permissions/node_modules/@nestjs/passport/dist/auth.guard.js:15:12) at MixinAuthGuard.canActivate (/home/user/workspace/permissions/node_modules/@nestjs/passport/dist/auth.guard.js:40:20) at GuardsConsumer.tryActivate (/home/user/workspace/permissions/node_modules/@nestjs/core/guards/guards-consumer.js:13:34) at canActivateFn (/home/user/workspace/permissions/node_modules/@nestjs/core/router/router-execution-context.js:97:59) at /home/user/workspace/permissions/node_modules/@nestjs/core/router/router-execution-context.js:47:37 at /home/user/workspace/permissions/node_modules/@nestjs/core/router/router-proxy.js:8:23 at Layer.handle [as handle_request] (/home/user/workspace/permissions/node_modules/express/lib/router/layer.js:95:5)

what am I doing wrong?

Upvotes: 6

Views: 13800

Answers (7)

Chandni
Chandni

Reputation: 11

To implement passport-jwt, you need to install the following dependencies.

  • npm install --save @nestjs/jwt passport-jwt
  • npm install --save-dev @types/passport-jwt

create jwt.strategy.ts file

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { USER_CONFIG } from '../config/constants';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: USER_CONFIG.JWT.SECRET,
    });
  }

  async validate(payload: any) {
    return {
      id: payload.id,
      email: payload.username,
    };
  }
}

Now define the JwtAuthGuard class which extends the built-in AuthGuard - file jwt-auth.guard.ts

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

It's time to define controllers in auth.controller.ts file. Use JwtAuthGuard with controller like @UseGuards(JwtAuthGuard)

import {
  Body,
  Controller,
  HttpCode,
  HttpStatus,
  Post,
  Put,
  Req,
  UseGuards,
  UsePipes,
} from '@nestjs/common';
import { ChangePasswordDTO, LoginDTO } from './dto/auth.dto';
import { AuthService } from './auth.service';
import { AuthGuard } from '@nestjs/passport';
import { Request } from 'express';
import { SUCCESS } from 'src/config/messages';
import { JwtAuthGuard } from './jwt-auth.guard';
import { changePasswordSchema } from './pipe/auth.pipe';
import { JoiValidationPipe } from 'src/pipes/joi-validation.pipe';

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

  @Post('/login')
  @UseGuards(AuthGuard('local'))
  @HttpCode(HttpStatus.OK)
  async login(@Req() req: Request, @Body() loginDTO: LoginDTO) {
    const result = await this.authService.login(loginDTO, req.user);
    return {
      message: SUCCESS.USER_LOGIN,
      data: result,
    };
  }

  @Put('/change-password')
  @UseGuards(JwtAuthGuard)
  @UsePipes(new JoiValidationPipe(changePasswordSchema))
  @HttpCode(HttpStatus.OK)
  async changePassword(
    @Body() changePasswordDTO: ChangePasswordDTO,
    @Req() req: Request,
  ) {
    await this.authService.changePassword(
      changePasswordDTO.currentPassword,
      changePasswordDTO.newPassword,
      req.user.id,
    );
    return {
      message: SUCCESS.PASSWORD_CHANGE,
    };
  }
}

Finally, register passport-jwt strategy in the module file.

import { Module } from '@nestjs/common';
import { UserModule } from 'src/user/user.module';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { USER_CONFIG } from '../config/constants';
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';

@Module({
  imports: [
    UserModule,
    PassportModule,
    JwtModule.register({
      secret: USER_CONFIG.JWT.SECRET,
      signOptions: { expiresIn: USER_CONFIG.JWT.EXPIRES_IN },
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService, LocalStrategy, JwtStrategy],
  exports: [AuthService],
})
export class AuthModule {}

Here, I have used passport-local strategy for authentication (for login purpose) and passport-jwt strategy for JSON Web Tokens.

Upvotes: 0

user2430821
user2430821

Reputation: 39

Please add the JwtStrategy as a provider in your module

@Module({
   imports: [...],
   providers: [JwtStrategy],
})

https://docs.nestjs.com/techniques/authentication

Upvotes: 2

user3633181
user3633181

Reputation: 21

I solved this problem by installing @types/passport. Using NestJS, TypeScript and JWT.

Upvotes: 2

Alaeddine.el
Alaeddine.el

Reputation: 26

Try to add the JwtStrategy as a provider in your module :

@Module({
  imports: [
   ....
  ],
  providers: [JwtStrategy],
})

and try again !

Upvotes: 1

CountZero
CountZero

Reputation: 196

solved. in PHP, we have one dependency tree for whole project. in npm each package has its own dependency subtree, e. g.:
--passport
--@pe/nest-kit
----passport
nest-kit uses objects from ----passport, but the root project uses objects of the type with the same name, but actually this is another type for the nodejs compiler. the solution is to re-export AuthGuard from @nestjs/passport through @pe/nest-kit, and it works.

Upvotes: 0

Riadh farhati
Riadh farhati

Reputation: 104

this is my code in github : https://github.com/riadhriadh/prototype_nestjs/tree/dev

in jwt.strategy.ts

    import * as passport from 'passport';
    import { ExtractJwt, Strategy } from 'passport-jwt';
    import { Injectable } from '@nestjs/common';
    import { AuthService } from '../auth.service';
    const  config_projet =require("./projet_config");
    @Injectable()
    export class JwtStrategy extends Strategy {
      constructor(private readonly authService: AuthService) {
        super(
          {
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            passReqToCallback: true,
            secretOrKey: config_projet.secret,
          },
          async (req, payload, next) => await this.verify(req, payload, next)
        );
        passport.use(this);
      }

      public async verify(req, payload, done) {
        const isValid = await this.authService.validateUser(payload);
        if (!isValid) {
          return done('Unauthorized', false);
        }
        done(null, payload);
      }
}

===================

in : auth.service.ts

import { Injectable } from '@nestjs/common';
import * as jwt from 'jsonwebtoken';
import { UsersService } from 'users/users.service';
const  config_projet =require("../projet_config");
var fs = require('fs');
@Injectable()
export class AuthService {

  constructor(private readonly usersService: UsersService) { }


  async createToken(email: string) {
    const expiresIn = 6000 * 60;
    const secretOrKey = fs.readFileSync("./key.pem");;
    const user = { email };

     const token = jwt.sign(user, secretOrKey,   { audience: 'urn:foo' });


    return { expires_in: expiresIn, token };
  }
  async validateUser(signedUser): Promise<boolean> {
    if (signedUser && signedUser.email) {
      return Boolean(this.usersService.getUserByEmail(signedUser.email));
    }

    return false;
  }
}

==============================

in : auth.controller.ts

================================

import { Controller, Post, HttpStatus, HttpCode, Get, Response, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersService } from 'users/users.service';
import { User } from 'users/user.entity';

@Controller("auth")
export class AuthController {
    constructor(
        private readonly authService: AuthService,
        private readonly userService: UsersService
    ) {}
    @Post('login')
    async loginUser(@Response() res: any, @Body() body: User) {
      if (!(body && body.email && body.password)) {
        return res.status(HttpStatus.FORBIDDEN).json({ message: 'Email and password are required!' });
      }

      const user = await this.userService.getUserByEmail(body.email);

      if (user) {
        if (await this.userService.compareHash(body.password, user.password)) {
          return res.status(HttpStatus.OK).json(await this.authService.createToken(user.email));
        }
      }

      return res.status(HttpStatus.FORBIDDEN).json({ message: 'Email or password wrong!' });
    } 
    @Post('register')
    async registerUser(@Response() res: any, @Body() body: User) {
      if (!(body && body.email && body.password && body.last_name && body.first_name)) {
        return res.status(HttpStatus.FORBIDDEN).json({ message: 'Username and password are required!' });
      }

      let user = await this.userService.getUserByEmail(body.email);

      if (user) {
        return res.status(HttpStatus.FORBIDDEN).json({ message: 'Email exists' });
      } else {
        let userSave = await this.userService.create(body);
       if(userSave){
         body.password=undefined;
       }
        return res.status(HttpStatus.OK).json(userSave);
      }
    }
}

Upvotes: 1

Pawel Smolaga
Pawel Smolaga

Reputation: 29

Are you sure that you have added all needed packages? Try to go through the authentication documentation https://docs.nestjs.com/techniques/authentication it explains very well how to deal with JWT.

Upvotes: 1

Related Questions