6

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?

James Fleming
  • 2,589
  • 2
  • 25
  • 41
CountZero
  • 186
  • 1
  • 1
  • 8

7 Answers7

2

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

2

Please add the JwtStrategy as a provider in your module

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

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

1

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.

  • 1
    Hello, this does not really answer the question. When you have enough reputation you'll be able to comment on posts in order to ask for clarification. – Henry Woody Oct 24 '18 at 18:46
1

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);
      }
    }
}
  • 2
    Welcome to StackOverflow and thank y9ou for your answer. Please provide an explanation for the questioner and future researchers so that people can learn from what you have provided rather than just copy a solution without understanding. – Alan Oct 24 '18 at 23:55
1

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

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

and try again !

0

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.

CountZero
  • 186
  • 1
  • 1
  • 8
0

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.

Chandni
  • 1
  • 2