Reputation: 17342
I'm passing a JWT from my client application (nextJS with nextAuth) via credentials header to my backend nestJS application (which is using graphQL). In my nestJS backend application I'm trying to implement an auth guard, so I extract the JWT with a custom function in my jwt.strategy.ts
But the JwtStrategy is not accepting my valid signed token. To prove that the JWT is valid, I put some console output for the token. But the validate()
function is never called. I do not understand why, as the token can be validated with jwt.verify
:
This is my output - it gets decoded by the jwt.verify()
:
JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7InVzZXJJZCI6MTIzLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiaXNBZG1pbiI6dHJ1ZX0sImlhdCI6MTYwOTY3NTc4Nn0.LQy4QSesxJR91PyGGb_0mGZjpw9hlC4q7elIDs2CkLo
Secret: uGEFpuMDDdDQA3vCtZXPKgBYAriWWGrk
Decoded: {
user: { userId: 123, username: 'username', isAdmin: true },
iat: 1609675786
}
I don't see, what I am missing and I even do not see how to debug it as there is no output in my jwt.strategy.ts and the validate-function is not called at all.
jwt.strategy.ts
import jwt from 'jsonwebtoken'
// import { JwtService } from '@nestjs/jwt'
import { Strategy } from 'passport-jwt'
import { PassportStrategy } from '@nestjs/passport'
import { Injectable } from '@nestjs/common'
import cookie from 'cookie'
import { getConfig } from '@myapp/config'
const { secret } = getConfig()
const parseCookie = (cookies) => cookie.parse(cookies || '')
const cookieExtractor = async (req) => {
let token = null
if (req?.headers?.cookie) {
token = parseCookie(req.headers.cookie)['next-auth.session-token']
}
// output as shown above
console.log('JWT:', token)
console.log('Secret:', secret)
const decoded = await jwt.verify(token, secret, { algorithms: ['HS256'] })
console.log('Decoded: ', decoded)
return token
}
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: cookieExtractor,
ignoreExpiration: true,
secretOrKey: secret
})
}
async validate(payload: any) {
console.log('payload:', payload) // is never called
return { userId: payload.sub, username: payload.username }
}
}
jwt-auth.guard.ts
import { Injectable, ExecutionContext } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
import { GqlExecutionContext } from '@nestjs/graphql'
@Injectable()
export class GqlAuthGuard extends AuthGuard('jwt') {
getRequest(context: GqlExecutionContext) {
const ctx = GqlExecutionContext.create(context)
return ctx.getContext().req
}
}
The guard is used in this resolver:
editor.resolver.ts
import { Query, Resolver } from '@nestjs/graphql'
import { UseGuards } from '@nestjs/common'
import { GqlAuthGuard } from '../auth/jwt-auth.guard'
@Resolver('Editor')
export class EditorResolvers {
constructor(private readonly editorService: EditorService) {}
@UseGuards(GqlAuthGuard)
@Query(() => [File])
async getFiles() {
return this.editorService.getFiles()
}
}
auth.module.ts
import { Module } from '@nestjs/common'
import { AuthController } from './auth.controller'
import { AuthService } from './auth.service'
import { PassportModule } from '@nestjs/passport'
import { LocalStrategy } from './local.strategy'
import { JwtStrategy } from './jwt.strategy'
import { UsersModule } from '../users/users.module'
import { JwtModule } from '@nestjs/jwt'
import { getConfig } from '@myApp/config'
const { secret } = getConfig()
@Module({
imports: [
UsersModule,
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.register({
secret,
verifyOptions: { algorithms: ['HS256'] },
signOptions: { expiresIn: '1d' }
})
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy, LocalStrategy],
exports: [AuthService]
})
export class AuthModule {}
The token is created on server side (nextJS api page) with:
const encode = async ({ secret, token }) => jwt.sign(token, secret, { algorithm: 'HS256' })
Upvotes: 2
Views: 2649
Reputation: 1498
I see 2 differences from nestJS docs examples from your jwt.strategy.ts file that you can change and give it a try..
https://docs.nestjs.com/security/authentication#implementing-passport-jwt
By default passport-jwt extractor we can see that is an sync and not async, so you can try change your extractor and remove the async, or to add await when calling it.
https://github.com/mikenicholson/passport-jwt/blob/master/lib/extract_jwt.js , look for fromAuthHeaderAsBearerToken function.
so or change your
const cookieExtractor = async (req) => {
to
const cookieExtractor = (req) => {
OR - add await when you call it
jwtFromRequest: await cookieExtractor(),
by the docs example in JwtStrategy constructor they calling the extractor and not passing it like you do
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
so try to call it in your JwtStrategy constructor
jwtFromRequest: cookieExtractor(), // (again - take care of sync / async)
Upvotes: 3