In my nestjs project, I am trying to use multiple jwt strategies.
Here is the jwt-auth.guard.ts:
export class JwtAuthGuard extends AuthGuard(['jwt', 'sec']) {}
jwt.strategy.ts:
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Inject, Injectable } from '#nestjs/common';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: ‘test’,
});
}
async validate(payload: any) {
return {
userId: payload.sub,
username: payload.username,
};
}
}
sec.strategy.ts:
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Inject, Injectable } from '#nestjs/common';
#Injectable()
export class JwtStrategysec extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: '1test',
});
}
async validate(payload: any) {
return {
userId: payload.sub,
username: payload.username,
};
}
}
Auth.module.ts:
#Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: 'test',
signOptions: { expiresIn: '2000000s' },
}),
],
providers: [
AuthService,
LocalStrategy,
JwtStrategysec,
JwtStrategy,
],
exports: [AuthService, JwtModule],
})
export class AuthModule {}
When I trying to use JwtAuthGuard in my code :
#UseGuards(JwtAuthGuard)
I can get the error:
ERROR [ExceptionsHandler] Unknown authentication strategy "sec"
Am I missing something here?
In your sec.strategy.ts you need to give the strategy a custom name like so:
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Inject, Injectable } from '#nestjs/common';
#Injectable()
export class JwtStrategysec extends PassportStrategy(Strategy, 'sec') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: '1test',
});
}
async validate(payload: any) {
return {
userId: payload.sub,
username: payload.username,
};
}
}
otherwise it will take on the default 'jwt' name. With the above, it'll now have the name 'sec' and be properly registered with passport
Related
I am trying to use jsonwebtoken with NestJs, and have been battling with this issue whereby NestJS returns an empty object once it encouters my code line to generate a token.
Each time, I try to generate a token, the server simply responds with an empty object.
Here is my jwt strategy code for passport
import { ConfigService } from "#nestjs/config";
import { ExtractJwt, Strategy } from "passport-jwt";
import { PassportStrategy } from "#nestjs/passport";
import { Inject, Injectable, UnauthorizedException } from "#nestjs/common";
import { RidersService } from "../riders/riders.service";
import { DriversService } from "../drivers/drivers.service";
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly riderService: RidersService,
private readonly driverService: DriversService,
#Inject(ConfigService) config: ConfigService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: config.get("JWT_SECRET"),
});
}
async validate(payload: {
role: string;
iat: number;
exp: number;
phone: string;
}) {
if (!payload) {
throw new UnauthorizedException();
}
const service = {
rider: this.riderService,
driver: this.driverService,
};
const user = await service[payload.role].findByAny(payload.phone);
if (!user) {
throw new UnauthorizedException();
}
// eslint-disable-next-line #typescript-eslint/no-unused-vars
const { iat, exp, ...rest } = payload;
return rest;
}
}
Now here is my AuthService stripped off
import { InjectModel } from "#nestjs/mongoose";
import { Injectable, Logger, HttpException } from "#nestjs/common";
import { JwtService } from "#nestjs/jwt";
#Injectable()
export class AuthService {
private readonly logger = new Logger(AuthService.name);
constructor(
private readonly jwtService: JwtService,
) {}
async register(
phone: string,
role: string,
): Promise<LoginResponseDTO | GENERIC_RESPONSE | unknown> {
return {
token: this.jwtService.sign({ role, phone }),
};
}
}
This line return { token: this.jwtService.sign({ role, phone }), } returns an empt object {} no matter what I do, once the code execution gets there, it stops and sends back an empty response.
Here is my Authmodule stripped off
import { JwtStrategy } from "./jwt.strategy";
import { Module } from "#nestjs/common";
import { AuthService } from "./auth.service";
import { AuthController } from "./auth.controller";
import { PassportModule } from "#nestjs/passport";
import { JwtModule } from "#nestjs/jwt";
import { ConfigModule, ConfigService } from "#nestjs/config";
#Module({
imports: [
PassportModule,
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
secret: configService.get<string>("JWT_SECRET"),
signOptions: { expiresIn: configService.get<string>("JWT_EXPIRESIN") },
}),
inject: [ConfigService],
}),
],
providers: [
AuthService,
JwtStrategy,
],
exports: [AuthService],
controllers: [AuthController],
})
export class AuthModule {}
Here is my Authcontroller stripped off
import { AuthService } from "./auth.service";
import { BadRequestException, Controller, Post, Query } from "#nestjs/common";
import { ParsePhonePipe } from "./../pipes/transform-phone.pipes";
#Controller("auth")
export class AuthController {
constructor(private readonly authService: AuthService) {}
#Post("signup")
async register(
#Query("phone", new ParsePhonePipe()) phone: string,
#Query("role") role: string,
) {
if (!role) {
throw new BadRequestException("user role is missing");
}
try {
const response = await this.authService.register(phone, role);
console.log(response, "RESPONSE");
return response;
} catch (error) {
return error;
}
}
}
Here is Appmodule stripped of
import { ConfigModule, ConfigService } from "#nestjs/config";
import { Module } from "#nestjs/common";
import { APP_GUARD } from "#nestjs/core";
import { AuthModule } from "./auth/auth.module";
import { JwtAuthGuard } from "./auth/jwt.auth.guard";
#Module({
imports: [
AuthModule,
MongooseModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
uri: configService.get<string>("RYDR_DB_URI"),
}),
inject: [ConfigService],
}),
ConfigModule.forRoot({
isGlobal: true,
}),
],
providers: [
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
TripHistoryService,
DriversService,
],
})
export class AppModule {}
The login response has nothing to do with the jwt as there are three possible response types defined.
I am unable to generate jwt token and it simply exit with an empty object {}, there is no error whatsoever.
What might I be doing wrong?
I tried to use jsonwebtoken package itself, expecting that there might be an issue with NestJs but it still failed to generate any token instead it returned an empty object as a response.
i'm facing an issue with NestJS :
"[Nest] 5068 - 08/11/2021, 3:12:02 PM ERROR [ExceptionHandler] Nest can't resolve dependencies of the AppService (?). Please make sure that the argument UserRepository at index [0] is available in the AppModule context."
I tried to add AppService in the imports with no luck.
How to evade this error?
app.module.ts :
import { Module } from '#nestjs/common';
import { TypeOrmModule } from '#nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { User } from './user.entity';
#Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'password',
database: 'test',
entities: [User],
synchronize: true,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule { }
app.controller.ts :
import { Body, Controller, Get, Post } from '#nestjs/common';
import { AppService } from './app.service';
import * as bcrypt from 'bcrypt';
#Controller('api')
export class AppController {
constructor(private readonly appService: AppService) {
}
#Post('register')
async register(
#Body('name') name: string,
#Body('email') email: string,
#Body('password') password: string,
#Body('phone') phone: string,
) {
const hashedPassword = await bcrypt.hash(password, 12);
return this.appService.create({
name,
email,
password: hashedPassword,
phone,
})
}
}
app.service.ts :
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
#Injectable()
export class AppService {
constructor(
#InjectRepository(User) private readonly userRepository: Repository<User>
) {
}
async create(data: any): Promise<User> {
return this.userRepository.save(data)
}
}
user.entiry.ts :
import { Column, Entity, PrimaryColumn } from "typeorm";
#Entity('users')
export class User {
#PrimaryColumn()
id: number;
#Column()
name: string;
#Column()
email: string;
#Column()
phone: string;
#Column()
password: string;
}
Thank you
You need to add TypeormModule.forFeature([User]) to your imports in your AppModule to set up the #InjectRepository(User) that you make use of. With the TypeORM module, forRoot/Async is for database connection and general TypeORM configuration, forFeature is for dynamic provider setup
I am a newbie in NestJs world. As far as I know, I imported everything needed in the JwtStrategy. I don't know where it went wrong. Can somebody help me with this?
As far as I referred to documetation, Whenever we want to use any entity in a module, we should import that entity in the imports field in the #Module() decorator. I did it.
jwt.strategy.ts
import { Injectable, UnauthorizedException } from "#nestjs/common";
import { PassportStrategy } from "#nestjs/passport";
import { Strategy, ExtractJwt } from "passport-jwt";
import { InjectRepository } from "#nestjs/typeorm";
import { Repository } from "typeorm";
import { UserEntity } from "src/entities/user.entity";
import { AuthPayload } from "src/common/dtos/user.dto";
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
#InjectRepository(UserEntity)
private userRepo: Repository<UserEntity>
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.SECRETKEY
});
}
async validate(payload: AuthPayload): Promise<UserEntity> {
const { username } = payload;
const user = this.userRepo.findOne({ where: { username: username } });
if(!user) {
throw new UnauthorizedException();
}
return user;
}
}
auth.module.ts
import { Module } from '#nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { TypeOrmModule } from '#nestjs/typeorm';
import { UserEntity } from 'src/entities/user.entity';
import { JwtModule } from '#nestjs/jwt';
import { PassportModule } from '#nestjs/passport';
import { JwtStrategy } from './jwt.strategy';
#Module({
imports: [
TypeOrmModule.forFeature([UserEntity]),
JwtModule.register({
secret: process.env.SECRETKEY,
}),
PassportModule.register({
defaultStrategy: 'jwt'
})
],
providers: [AuthService, JwtStrategy],
controllers: [AuthController],
exports: [PassportModule, JwtStrategy]
})
export class AuthModule {}
user.entity.ts
import { Entity, Column, OneToMany, JoinTable, BeforeInsert } from "typeorm";
import { AbstractEntity } from "./abstract-entity.abstract";
import { IsEmail } from "class-validator";
import { Exclude, classToPlain } from "class-transformer";
import * as bcrypt from "bcryptjs";
import { CategoryEntity } from "./category.entity";
import { ArticleEntity } from "./article.entity";
#Entity('User')
export class UserEntity extends AbstractEntity {
#Column({
type: "varchar",
length: 80
})
fullName: string;
#Column({
type: "varchar",
unique: true
})
#IsEmail()
email: string;
#Column({
type: "varchar",
unique: true
})
username: string;
#Column({
type: "varchar"
})
#Exclude()
password: string;
#Column({
default: null,
nullable: true
})
avatar: string | null;
#Column({
type: "varchar",
unique: true
})
phoneNumber: string;
#Column({
type: "boolean",
default: false
})
isAdmin: boolean;
#Column({
type: "boolean",
default: false
})
isStaff: boolean;
#Column({
type: "boolean",
default: false
})
isEmailVerified: boolean;
#OneToMany(type => CategoryEntity, category => category.createdBy)
#JoinTable()
categories: CategoryEntity[];
#OneToMany(type => ArticleEntity, article => article.createdBy)
#JoinTable()
articles: ArticleEntity[];
#BeforeInsert()
async hashPassword() {
this.password = await bcrypt.hash(this.password, 10);
}
async comparePassword(attempt: string): Promise<boolean> {
return await bcrypt.compare(attempt, this.password);
}
toJSON(): any {
return classToPlain(this);
}
}
app.module.ts
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from "#nestjs/typeorm";
import { APP_FILTER, APP_INTERCEPTOR } from '#nestjs/core';
import {
DatabaseConnectionService
} from "./utils/database-connection.service";
import { AuthModule } from './auth/auth.module';
import { UsersModule } from './users/users.module';
import { ArticlesModule } from './articles/articles.module';
import { HttpExceptionFilter } from './common/exception-filters/http-exception.filter';
import { ResponseInterceptor } from './common/interceptors/response.interceptor';
import { CategoryModule } from './category/category.module';
#Module({
imports: [
TypeOrmModule.forRootAsync({
useClass: DatabaseConnectionService
}),
AuthModule,
UsersModule,
ArticlesModule,
CategoryModule,
],
controllers: [AppController],
providers: [
// {
// provide: APP_INTERCEPTOR,
// useClass: ResponseInterceptor
// },
{
provide: APP_FILTER,
useClass: HttpExceptionFilter
},
AppService
],
})
export class AppModule {}
database-connection.service.ts
import { Injectable } from "#nestjs/common";
import { TypeOrmOptionsFactory, TypeOrmModuleOptions } from "#nestjs/typeorm";
import { truncate } from "fs";
#Injectable()
export class DatabaseConnectionService implements TypeOrmOptionsFactory {
createTypeOrmOptions(): TypeOrmModuleOptions {
return {
type: "mysql",
host: process.env.HOST,
port: parseInt(process.env.PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DATABASE,
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
dropSchema: true,
autoLoadEntities: true,
logger: "simple-console"
};
}
}
The Error is as follows:
Based on your error, somewhere you have JwtStrategy in your imports array. If you need the JwtStrategy you should instead import the AuthModule, as providers should only be in the providers array and should never be in imports.
Consider to move to Active Record pattern. All what you need to do is just to let your AbstractEntity extends BaseEntity of TypeOrm.
You can remove all typeorm features imports like:
TypeOrmModule.forFeature([UserEntity])
and all dependency injections for repository like:
#InjectRepository(UserEntity)
private userRepo: Repository<UserEntity>
Just use the entity class for querying:
const user = await UserEntity.findOne({ where: { username } });
auth.module.ts
import { Module, forwardRef } from '#nestjs/common';
import { AuthService } from './auth.service';
import { UserService } from '../user/user.service';
import { PassportModule } from '#nestjs/passport';
import { LocalStrategy } from './local.strategy';
import { JwtModule, JwtService } from '#nestjs/jwt';
import { MongooseModule } from '#nestjs/mongoose';
import { ConfigModule } from '#nestjs/config';
import { JwtStrategy } from './jwt.strategy';
import { UserModule } from 'src/user/user.module';
import { User, UserSchema } from '../user/schemas/user.schema';
#Module({
providers: [AuthService, LocalStrategy, UserService, JwtService, JwtStrategy],
imports: [
ConfigModule.forRoot({ isGlobal: true }),
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
PassportModule,
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: '1800s' },
}),
forwardRef(() => UserModule),
],
exports: [AuthService, JwtService],
})
export class AuthModule {}
user.module.ts
import { Module, forwardRef } from '#nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { AuthModule } from '../auth/auth.module';
import { AuthService } from '../auth/auth.service';
import { MongooseModule } from '#nestjs/mongoose';
import { User, UserSchema } from './schemas/user.schema';
#Module({
imports: [
forwardRef(() => AuthModule),
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
],
providers: [UserService, AuthService],
controllers: [UserController],
exports: [UserService],
})
export class UserModule {}
error message
Nest can't resolve dependencies of the JwtService (?). Please make sure that the argument JWT_MODULE_OPTIONS at index [0] is available in the AuthModule context.
I'm importing Jwt from nest. Once Mongoose is added to auth.module I get endless errors when I try and update anything. I had a hard coded array to test out routes before and it worked just fine.
You don't need to re-add provides to the providers array if they are provided from another module. Doing this will make Nest instantiate a new instance of the provider (breaking the singleton scoping), and will require you to have all of the configuration necessary for the provider (in this case, the JWT_MODULE_OPTIONS
I'm trying to build this simple MEAN stack app with the simple functionality of registering, login in and then viewing the profile.
So, where does the error occur? It pops up when this newbie coder tries to log in to this simple app
Here's a screenshot of it
Here are the codes for you to see the errors that I might have made.
This is the code belonging to the login component
import { Component, OnInit } from '#angular/core';
import { AuthService } from '../../services/auth.service';
import { Router } from '#angular/router'
import { FlashMessagesService } from 'angular2-flash-messages';
import { Observable } from 'rxjs/Rx';
import { map, subscribeOn } from 'rxjs/operators';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
username: String;
password: String;
constructor(
private authService: AuthService,
private router: Router,
private flashMessage: FlashMessagesService
) { }
ngOnInit(): void {
}
onLoginSubmit() {
console.log(this.username, this.password);
const user = {
username: this.username,
password: this.password
}
this.authService.authenticateUser(user).subscribe(data => {
console.log(data);
if (data) {
this.authService.storeUserData(data.token, data.user);
this.flashMessage.show('You are now logged in', {
cssClass: 'alert-success',
timeout: 5000
});
this.router.navigate(['/dashboard']);
}
else {
this.flashMessage.show('incorrect information you stupidass', {
cssClass: 'alert-danger',
timeout: 5000
});
this.router.navigate(['/login']);
}
});
}
}
The next one is of the authentication service file
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
import { map } from 'rxjs/operators';
import { tokenNotExpired } from 'angular2-jwt';
#Injectable({
providedIn: 'root'
})
export class AuthService {
authToken: any;
user: any;
constructor(private http: Http) { }
registerUser(user: { name: String; email: String; username: String; password: String; }) {
let headers = new Headers();
//adding a value to the header and connecting to the backend
headers.append('Content-Type', 'application/json');
return this.http.post('http://localhost:3000/users/register', user, { headers: headers }).pipe(map((res: { json: any; }) => res.json));
}
authenticateUser(user: { username: String; password: String; }) {
let headers = new Headers();
//adding a value to the header and connecting to the backend
headers.append('Content-Type', 'application/json');
return this.http.post('http://localhost:3000/users/authenticate', user, { headers: headers }).pipe(map((res: { json: any; }) => res.json));
}
getProfile() {
let headers = new Headers();
this.loadToken();
headers.append('Authroization', this.authToken);
//adding a value to the header and connecting to the backend
headers.append('Content-Type', 'application/json');
return this.http.get('http://localhost:3000/users/profile', { headers: headers }).pipe(map((res: { json: any; }) => res.json));
}
storeUserData(token, user) {
localStorage.setItem('id_token', token);
localStorage.setItem('user', JSON.stringify(user));
this.authToken = token;
this.user = user;
}
loadToken() {
const token = localStorage.getItem('id_token');
this.authToken = token;
}
loggedIn() {
return tokenNotExpired(null, localStorage.getItem('id_token'));
}
logout() {
this.authToken = null;
this.user = null;
localStorage.clear();
}
}
//this.myObservable().pipe(map(data => {}))
And this last one is of the app.component file
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { HttpModule } from '#angular/http';
import { RouterModule, Routes } from '#angular/router';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { NavbarComponent } from './components/navbar/navbar.component';
import { LoginComponent } from './components/login/login.component';
import { RegisterComponent } from './components/register/register.component';
import { HomeComponent } from './components/home/home.component';
import { DashboardComponent } from './components/dashboard/dashboard.component';
import { ProfileComponent } from './components/profile/profile.component';
import { JwtModule } from '#auth0/angular-jwt';
import { FormsModule } from '#angular/forms';
import { ValidateService } from './services/Validate.service';
import { FlashMessagesModule } from 'angular2-flash-messages';
import { AuthService } from './services/auth.service';
import { AuthGuard } from './guards/auth.guard';
export function tokenGetter() {
return localStorage.getItem('access_token');
}
const appRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'register', component: RegisterComponent },
{ path: 'login', component: LoginComponent },
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] },
{ path: 'profile', component: ProfileComponent, canActivate: [AuthGuard] },
]
#NgModule({
declarations: [
AppComponent,
NavbarComponent,
LoginComponent,
RegisterComponent,
HomeComponent,
DashboardComponent,
ProfileComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
RouterModule.forRoot(appRoutes),
FlashMessagesModule.forRoot(),
JwtModule.forRoot({
config: {
tokenGetter: tokenGetter,
whitelistedDomains: ['localhost:4000'],
blacklistedRoutes: ['localhost:4000/api/auth']
}
})
],
providers: [ValidateService, AuthService, AuthGuard],
bootstrap: [AppComponent]
})
export class AppModule { }
Why does this error occur?
Edit: Below are the screenshot of postman requests that I did for this app. There you can find the value of the token as well.
The authentication request is what you see here:
This next one is for the get request done to get into the profile: