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.
Related
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 }),
};
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
I am trying to use Passport for SSO. My problem is that when I log in with any of the options everything is fine, except the data saving... I think the functions in the strategy files are not called (the log is not working neither).
For example the Google strategy:
#Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(private userService: UserService) {
super({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: 'http://localhost:4200',
scope: ['email', 'profile'],
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: any,
done: VerifyCallback,
): Promise<any> {
try {
console.log(profile);
const user = profile;
this.userService.FindOrCreate(profile);
done(null, user);
} catch (err) {
done(err, null);
}
}
}
Controller:
#Get('google')
#UseGuards(AuthGuard('google'))
async twitterauth(#Req() req) {
return await this.authService.login(req.user);
}
AuthService:
#Injectable()
export class AuthService {
private readonly logger = new Logger(AuthService.name);
constructor(
private userService: UserService,
private readonly jwtService: JwtService,
) {}
async validateUser(email: string, password: string): Promise<User> {
const user: User = await this.userService.findOne({
where: { email },
});
if (!user) {
return null;
} else {
if (await bcrypt.compare(password, user.password)) {
return user;
} else {
this.logger.error('Password is incorrect.');
return null;
}
}
}
async login(user: any) {
const payload = { email: user.email, role: user.role };
return {
// eslint-disable-next-line #typescript-eslint/camelcase
access_token: this.jwtService.sign(payload),
};
}
}
The other strategies (fb, linkedin, instagram, github) are quite the same and the problem is the same.
The problem, as found in chat, was that the callback that Google was calling to in the OAuth flow, was not a part of the same server, and as such, the NestJS server could not react to the incoming data, hence why the validate was never called.
That callback route needs to point to your NestJS server so that it can handle the saving logic for the database,OR the angular applications needs to re-route the return to it back to the NestJS server. Either way, your validations aren't being called because your Nest server never gets the callback with all the sensitive information from Google
More than likely, it will be better to have the callback pointed at your server so that the data is formatted as Passport is expected.
I'm trying to secure a nestjs api project using a jwt generated by google oauth. I have the client code working and have verified with jwt.io that the jwt being generated is correct and validates ok with the client secret I have.
I have followed the guide from nestjs for implementing passport and the jwt auth guard, however all I get when I pass a bearer token to an method with the guard is JsonWebTokenError: invalid algorithm
The relevant code snippits:
jwt.strategy.ts
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: true,
secretOrKey: privateKey,
algorithms: ['HS256']
});
}
// eslint-disable-next-line #typescript-eslint/no-explicit-any
async validate(payload: any) {
const user = await this.authService.validateUser(+payload.sub);
console.log(user);
return user;
}
}
jwt.auth-guard.ts
export class JwtAuthGuard extends AuthGuard('jwt') {
handleRequest(err, user, info, context) {
// valid Token: empty log line
// invalid Token: empty log line
console.log(err, 'error');
// valid Token: token object
// invalid Token: false
console.log(user, 'user');
// valid Token: empty log line
// invalid Token: error object from passport
console.log(info, 'info'); <---- ERROR COMES FROM HERE
// valid Token: no log line at all
// invalid Token: no log line at all
console.log(context, 'context');
if (err || !user) {
console.error(`JWT Authentication error: ${err}`);
throw err || new UnauthorizedException();
}
return user;
}
}
auth.module.ts
imports: [
PassportModule.register({ defaultStrategy: 'jwt', session: true }),
JwtModule.register({
secretOrPrivateKey: privateKey,
signOptions: {
expiresIn: 3600,
algorithm: 'HS256'
}
}),
UsersModule
],
providers: [AuthService, JwtStrategy],
exports: [AuthService]
})
export class AuthModule {}
I using nest.js + passport + jwt + graphql in project.
If there is a token, then decoded information,
want to get undefined if don't have token.
Always must have Guards to receive decoded tokens.
Can I selectively generate 401 error?
#Module({
providers: [
JwtStrategy,
],
imports: [
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.register({
secret: 'hi',
signOptions: { expiresIn: '1d' },
}),
],
})
export class AuthModule {
}
export class GqlAuthGuard extends AuthGuard('jwt') {
getRequest(context: ExcutionContext) {
const ctx = GqlExecutionContext.create(context)
return ctx.getContext().req
}
}
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: config.jwt.secret,
})
}
validate(payload: JwtPayload): any {
return { user_id: +payload.user_id , username: payload.username }
}
}
#Resolver(() => Test)
export class TestResolver {
#UseGuards(GqlAuthGuard) // I want to get validated users without guards.
#Query(() => Test)
startTest(
#User() user: any, // req.user
) {
console.log(user)
}
}
I don't know if it's possible, but I want this code.
app.use((req, res, next) => {
if (req.headers.token) { // optional
try {
req.user = verify(req.headers.token)
} catch (err) {
req.user = undefined
}
}
next()
})
app.get((req, res, next) => {
if (req.user) {
...
}
})
You can apply global guards like so
async function bootstrap(): Promise<void> {
const app = await NestFactory.create(AppModule);
const reflector = app.get(Reflector);
app.useGlobalGuards(new GqlAuthGuard(reflector));
await app.listen(process.env.PORT);
}