refresh token API error "secretOrPrivateKey must have a value" - node.js

When a user logs into the API generates a token so that he has access to other endpoints, but the token expires in 60sec, I made a function to generate a new valid token using the old token (which was stored in the database), but when I'm going to generate a new valid token I'm getting the secretOrPrivateKey must have a value error
The function refreshToken use function login to generate a new token
Nest error:
secretOrPrivateKey must have a value
Error: secretOrPrivateKey must have a value
at Object.module.exports [as sign] (C:\Users\talis\nova api\myflakes_api\node_modules\jsonwebtoken\sign.js:107:20)
at JwtService.sign (C:\Users\talis\nova api\myflakes_api\node_modules\#nestjs\jwt\dist\jwt.service.js:28:20)
at AuthService.login (C:\Users\talis\nova api\myflakes_api\src\auth\auth.service.ts:18:39)
at TokenService.refreshToken (C:\Users\talis\nova api\myflakes_api\src\token\token.service.ts:39:37)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at TokenController.refreshToken (C:\Users\talis\nova api\myflakes_api\src\token\token.controller.ts:12:16)
at C:\Users\talis\nova api\myflakes_api\node_modules\#nestjs\core\router\router-execution-context.js:46:28
at C:\Users\talis\nova api\myflakes_api\node_modules\#nestjs\core\router\router-proxy.js:9:17
My code:
Function refreshToken in the file token.service.ts
async refreshToken(oldToken: string) {
let objToken = await this.tokenRepository.findOne({hash: oldToken})
if (objToken) {
let user = await this.userService.findOneOrFail({email:objToken.email})
return this.authService.login(user)
} else {
return new UnauthorizedException(MessagesHelper.TOKEN_INVALID)
}
}
Function login in the file auth.service.ts
async login(user: UsersEntity) {
const payload = { email: user.email, sub: user.idUser }
const token = this.jwtService.sign(payload) // here!!!
this.tokenService.save(token, user.email)
return {
token: token
};
}
Error is on const token = this.jwtService.sign(payload)
Here is the file jwt.strategy.ts
import { Injectable } from "#nestjs/common";
import { PassportStrategy } from "#nestjs/passport";
import { ExtractJwt, Strategy } from "passport-jwt";
import { jwtConstants } from "../constants";
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
async validate(payload: { sub: any; email: any; }) {
return { id: payload.sub, email: payload.email}
}
}
And here local.strategy.ts
import { Injectable, UnauthorizedException } from "#nestjs/common";
import { PassportStrategy } from "#nestjs/passport";
import { Strategy } from "passport-local";
import { MessagesHelper } from "src/helpers/messages.helper";
import { AuthService } from "../auth.service";
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({ usernameField: 'email' });
}
async validate(email: string, password: string): Promise<any> {
const user = await this.authService.validateUser(email, password);
if(!user)
throw new UnauthorizedException(MessagesHelper.PASSWORD_OR_EMAIL_INVALID)
return user;
}
}
this is the AuthModule where is JwtModule.register
#Module({
imports: [
ConfigModule.forRoot(),
UsersModule,
PassportModule,
TokenModule,
JwtModule.register({
secret: jwtConstants.secret,
signOptions: { expiresIn: '60s' },
}),
],
controllers: [AuthController],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [JwtModule, AuthService]
})
export class AuthModule {}
Guys i tried to use images, but i'm new user and i still don't have a reputation, sorry.

Doing what #Micael Levi mentioned in the comments worked for me, so it would be:
const token = this.jwtService.sign(payload, jwtConstants.secret)
For future reference, I encountered this issue despite my environment variables being defined (process.env.SECRET_KEY being undefined was a common problem seen in other similar questions). So what I did to fix mine was:
return {
access_token: this.jwtService.sign(payload, { secret: process.env.JWT_SEC }),
};

Related

Nest.js Passport.js Google conflicts with Apple

I have created auth for application by google (passport-google-oauth20) and apple (passport-appleid). When i try to invoke GET google-sign-in, I have the following error
[Nest] 987 - 07/22/2022, 9:49:18 AM ERROR [ExceptionsHandler] Cannot read properties of undefined (reading 'callbackURL')
TypeError: Cannot read properties of undefined (reading 'callbackURL')
at GoogleStrategy.OAuth2Strategy.authenticate (/home/node/app/node_modules/passport-appleid/lib/strategy.js:76:59)
at attempt (/home/node/app/node_modules/passport/lib/middleware/authenticate.js:369:16)
at authenticate (/home/node/app/node_modules/passport/lib/middleware/authenticate.js:370:7)
at /home/node/app/node_modules/#nestjs/passport/dist/auth.guard.js:96:3
at new Promise (<anonymous>)
at /home/node/app/node_modules/#nestjs/passport/dist/auth.guard.js:88:83
at MixinAuthGuard.<anonymous> (/home/node/app/node_modules/#nestjs/passport/dist/auth.guard.js:49:36)
at Generator.next (<anonymous>)
at fulfilled (/home/node/app/node_modules/#nestjs/passport/dist/auth.guard.js:17:58)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
If i commented Apple Strategy in user module, everything work fine.
I can't understand why GoogleStrategy.OAuth2Strategy.authenticate go to the appleid packages
And If I use only Google Strategy or only Apple Strategy everything works.
#UseGuards(AuthGuard('google'))
#ApiOperation({ summary: 'google sign in' })
#Get('google-sign-in')
public async signInWithGoogle(#Req() req: any) {
return req.user;
}
#UseGuards(AuthGuard('google'))
#ApiOperation({ summary: 'google redirect' })
#Get('auth/google/redirect')
public async signInWithGoogleRedirect(#Req() req: any) {
return this.userService.socialSignIn(req.user);
}
#UseGuards(AuthGuard('apple'))
#ApiOperation({ summary: 'apple sign in' })
#Get('apple-sign-in')
public async signInWithApple(#Req() req: any) {
return req.user;
}
#UseGuards(AuthGuard('apple'))
#ApiOperation({ summary: 'apple redirect' })
#Get('apple/redirect')
public async signInWithAppleRedirect(#Req() req: any) {
return this.userService.socialSignIn(req.user);
}
import { Injectable } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { Profile } from 'passport';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { RegisteredPlatformsEnum } from 'src/_enum/registered-platforms.enum';
import { ConfigService } from '../config.services';
#Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(configService: ConfigService) {
super({
clientID: configService.get('GOOGLE_AUTH_CLIENT_ID'),
clientSecret: configService.get('GOOGLE_AUTH_CLIENT_SECRET'),
callbackURL: configService.get('GOOGLE_AUTH_CALLBACK_URL'),
scope: ['email', 'profile'],
});
}
public async validate(
accessToken: string,
refreshToken: string,
profile: Profile,
done: VerifyCallback,
): Promise<any> {
const { name, emails } = profile;
const [userEmail] = emails;
const user = {
email: userEmail.value,
name: name.givenName,
registeredPlatform: RegisteredPlatformsEnum.GOOGLE,
accessToken,
};
done(null, user);
}
}
import path from 'path';
import { Injectable } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { Profile } from 'passport';
import { Strategy, VerifyCallback } from 'passport-appleid';
import { RegisteredPlatformsEnum } from 'src/_enum/registered-platforms.enum';
import { ConfigService } from '../config.services';
#Injectable()
export class AppleStrategy extends PassportStrategy(Strategy, 'apple') {
constructor(configService: ConfigService) {
super({
clientID: configService.get('APPLE_AUTH_CLIENT_ID'),
callbackURL: configService.get('APPLE_AUTH_CALLBACK_URL'),
teamId: configService.get('APPLE_AUTH_TEAM_ID'),
keyIdentifier: configService.get('APPLE_AUTH_KEY_IDENTIFIER'),
privateKeyPath: path.join(
__dirname,
`./AuthKey_${configService.get('APPLE_AUTH_KEY_IDENTIFIER')}.p8`,
),
});
}
public async validate(
accessToken: string,
refreshToken: string,
profile: Profile,
done: VerifyCallback,
): Promise<any> {
const { name, emails } = profile;
const [userEmail] = emails;
const user = {
email: userEmail.value,
name: name.givenName,
registeredPlatform: RegisteredPlatformsEnum.APPLE,
accessToken,
};
done(null, user);
}
}
import { Module, forwardRef, Logger } from '#nestjs/common';
import { TypeOrmModule } from '#nestjs/typeorm';
import { AppModule } from '../../app.module';
import { ConfigService } from '../../services/config.services';
import { UserService } from './services/users.service';
import { UserController } from './user.controller';
import { UserEntity } from '../../model/user.entity';
import { EmailService } from '../../services/email.service';
import { UserRepository } from 'src/repositories/user.repository';
import { GoogleStrategy } from 'src/services/strategy/google-strategy';
import { AppleStrategy } from 'src/services/strategy/apple-strategy';
#Module({
imports: [
forwardRef(() => AppModule),
TypeOrmModule.forFeature([UserEntity, UserRepository]),
],
controllers: [UserController],
providers: [
Logger,
UserService,
ConfigService,
EmailService,
// passport strategies
AppleStrategy,
GoogleStrategy,
],
exports: [UserService],
})
export class UserModule {}

NestJS passport-jwt always throws Unauthorized

I encountered to the issue that whenever I sign up through the postman I receive the token. However, other route is private and requires token as Authorization Bearer, but whenever I put the token I receive "Unauthorized". The validate from strategy never executes, as I understand because the token for some reasons is invalid. Important to mention, that I do not receive any errors.
jwt.strategy.ts:
import { Injectable } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { PassportStrategy } from '#nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.get('JWT_SECRET'),
});
}
async validate(payload: any) {
console.log({ payload: payload });
return true;
}
}
auth.module.ts
import { Module } from '#nestjs/common';
import { JwtModule } from '#nestjs/jwt';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtStrategy } from './strategy';
#Module({
imports: [JwtModule.register({})],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
})
export class AuthModule {}
auth.service.ts where I receive token:
public async signin(body: AuthDto) {
// find the user by email
const user = await this.prisma['User'].findUnique({
where: {
email: body.email,
},
});
// if user does not exists throw exception
if (!user) {
throw new ForbiddenException('Invalid email address or password');
}
// compare passwords
const passwordMatches = await argon.verify(user.hash, body.password);
// if password is inoccrect throw exception
if (!passwordMatches) {
throw new ForbiddenException('Invalid email address or password');
}
return this.signToken(user.id, user.email);
}
auth.service.ts where I create the token:
private async signToken(userId: number, email: string): Promise<{ access_token: string }> {
const payLoad = {
sub: userId,
email,
};
const token = await this.jwt.sign(payLoad, {
expiresIn: '10m',
secret: this.config.get('JWT_SECRET'),
});
return { access_token: token };
}
}
user.controller.ts where the private route is
import { Controller, Get, UseGuards } from '#nestjs/common';
import { JwtGuard } from 'src/auth/guard';
#Controller('users')
export class UserController {
#UseGuards(JwtGuard)
#Get('me')
getMe() {
return 'Hello JWT';
}
}
jwt.guard.ts
import { AuthGuard } from '#nestjs/passport';
export class JwtGuard extends AuthGuard('jwt') {
constructor() {
super();
}
}
The code works fine, without any issues. However, the issues was with the postman. For some reasons the Authorization tab were bugged, tried to sent through headers written by hand and that worked without any problems

Nestjs Passport-jwt better Unauthorized strategy

just going through docs on authentication in NestJS: docs.nestjs.com
Here is the code:
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable } from '#nestjs/common';
import { jwtConstants } from './constants';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
According to docs validate method is called when request contains jwt and that jwt is valid.
I am wondering is there a callback method for the case when jwt is missing from request header, or jwt is invalid or expired. I would like to return response error with message to client that their token is expired or missing...
Thanks
You could implement a custom strategy and check headers or cookies how you like. This is a (shortened) exmaple I'am using on my app.
import { JwtService } from '#nestjs/jwt';
import { Strategy } from 'passport-custom';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'custom-jwt') {
constructor(private readonly jwtService: JwtService) {
super();
}
async validate(req: Request): Promise<any> {
const token = req.cookies.auth ?? req.headers.authorization;
if (!token) {
throw new UnauthorizedException();
}
const user = this.jwtService.decode(token, {
json: true
});
if (!user) {
throw new UnauthorizedException();
}
if (this.isExpired(user)) {
throw new UnauthorizedException();
}
return user;
}
private isExpired(user: JwtUserDto): boolean {
// ...
}
}
This code checks for a jwt token either in a "auth"-cookie or in "Authorization"-header and by returning user, it attaches the decoded user (if valid) to the request.
To use it: export class JwtAuthGuard extends AuthGuard('custom-jwt')
It's just an example, to see how it works. You might need to adapt it to fit your needs.

LocalAuthGuard not working in nestjs app with typeorm and passport-local

I am using nestjs 8.0 with typeorm, passport-jwt, and passport-local. Everything seems to be working fine other than the LocalAuthGuard. I am able to successfully create a new user and even use the routes that have JwtAuthGuard but LocalAuthGuard seems to have some issues as I keep getting 401 Unauthorized error
Also, is there a way to console log some output from within the LocalAuthGuard or LocalStrategy?
auth.controller.ts
#Controller(['admin', 'user'])
export class AuthController {
constructor(
private authService: AuthService,
) {}
#UseGuards(LocalAuthGuard)
#Post('login')
login(#Request() req) {
console.log('object');
if (req.path.includes('admin') && !req.user.isAdmin) {
throw new UnauthorizedException();
}
return this.authService.login(req.user);
}
...
}
local.guard.ts
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}
local.strategy.ts
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super();
}
async validate(usernameOrEmail: string, password: string): Promise<any> {
const user = await this.authService.validateUser({
usernameOrEmail,
password,
});
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
auth.service.ts
#Injectable()
export class AuthService {
constructor(
#InjectRepository(User)
private userRepository: Repository<User>,
private jwtService: JwtService,
) {}
async validateUser({ usernameOrEmail, password }: LoginDto) {
const user = (await this.userRepository.findOne({ username: usernameOrEmail })) ||
(await this.userRepository.findOne({ email: usernameOrEmail }));
if (user && (await bcrypt.compare(password, user.password))) {
return user;
}
return null;
}
...
}
auth.module.ts
#Module({
imports: [
TypeOrmModule.forFeature([User]),
PassportModule,
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
return {
secret: configService.get('JWT_KEY'),
signOptions: {
expiresIn: '6000s',
},
};
},
}),
],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService],
controllers: [AuthController],
})
export class AuthModule {}
Any help or suggestion is greatly appreciated.
EDIT
It seems for LocalAuthGuard, username, and password are a must and other properties are optional.
You can create multiple local strategies with different parameters. For example,
Username and password
Phone and OTP
Email and password
Email and OTP
For using multiple local strategies, refer to this answer
Then, you can also pass an options object to specify different property names, for example: super({ usernameField: 'email', passwordField: 'otp' })
implement local strategy as follows
import { Strategy } from 'passport-local';
import { PassportStrategy } from '#nestjs/passport';
import { Inject, Injectable, UnauthorizedException } from '#nestjs/common';
import { IAuthService } from '../services';
import { LoginDto, OtpLoginDto } from '../dto';
import { UserDto } from 'src/modules/user/dto';
import { plainToClass } from 'class-transformer';
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(
#Inject(IAuthService)
private readonly authService: IAuthService,
) {
super({
usernameField: 'user_name',
passwordField: 'otp',
});
}
async validate(user_name: string, otp: string): Promise<any> {
const loginDto = plainToClass(OtpLoginDto, {
username: user_name,
otp: otp,
});
const user: UserDto = await this.authService.verifyOtp(loginDto);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
ref: customize passport

Passport JwtStrategy never executed in nestjs

I am using nestjs and having an issue with using guards to authenticate a request.
My JwtStrategy is never execute.
Here is my JwtStrategy :
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(
#Inject('IQueryBusAdapter')
private readonly queryBus: IQueryBusAdapter,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET_KEY,
});
}
validate = async (payload: IJwtPayload) => {
Logger.log('INNNNN');
const query = new GetUserByIdQuery();
query.id = payload.id;
const user = await this.queryBus.execute(query);
if (!(user instanceof User)) {
throw new UnauthorizedException();
}
return user;
};
}
I also try to call directly validate() directly in constructor but nothing change...
My AuthModule :
#Module({
imports: [
BusModule,
JwtModule.register({
secretOrPrivateKey: process.env.JWT_SECRET_KEY,
signOptions: {
expiresIn: process.env.JWT_EXPIRES,
},
}),
PassportModule.register({ defaultStrategy: 'jwt' }),
TypeOrmModule.forFeature([User]),
],
controllers: [RegisterAction, LoginAction],
providers: [
JwtStrategy,
],
})
export class AuthModule {}
My controller
#Get('/:id')
#ApiOperation({ title: 'Get user ressource' })
#UseGuards(AuthGuard('jwt'))
async index(#Param() query: GetUserByIdQuery): Promise<object> {
const user = await this.queryBus.execute(query);
if (!(user instanceof User)) {
throw new NotFoundException();
}
return {
id: user.id,
fullName: user.getFullName(),
email: user.email
};
}
I always received 401 status code.
Thanks for your help.
validate will only be called when you pass a valid jwt token. When the token is signed with a different secret or is expired, validate will never be called. Make sure you have a valid token. You can check your token with the jwt debugger.

Resources