A legitimate JWT is deemed invalid for nestJS guard

I am currently facing an issue with passing a JWT from my client application, which is built using nextJS with nextAuth, to my backend nestJS application that utilizes graphQL. In order to implement an auth guard in my nestJS backend application, I'm attempting to extract the JWT using a custom function in my jwt.strategy.ts

However, despite having a valid signed token, the JwtStrategy is refusing to accept it. To validate the validity of the JWT, I added some console output for the token. Strangely, the validate() function is never triggered. It's puzzling because the token can be successfully verified using jwt.verify:

This is the decoded output from the jwt.verify():

JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7InVzZXJJZCI6MTIzLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwiaXNBZG1pbiI6dHJ1ZX0sImlhdCI6MTYwOTY3NTc4Nn0.LQy4QSesxJR91PyGGb_0mGZjpw9hlC4q7elIDs2CkLo
Secret: uGEFpuMDDdDQA3vCtZXPKgBYAriWWGrk
Decoded: {
  user: { userId: 123, username: 'username', isAdmin: true },
  iat: 1609675786
}

I am unable to pinpoint the missing piece of the puzzle and I'm at a loss as to how to debug this further, especially since there is no output in my jwt.strategy.ts file and the validate-function remains untouched.

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 implemented 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 generated on the server side (nextJS api page) using:

const encode = async ({ secret, token }) => jwt.sign(token, secret, { algorithm: 'HS256' })

Answer №1

I noticed two discrepancies between the nestJS documentation examples and your jwt.strategy.ts file that you may want to consider changing.

https://docs.nestjs.com/security/authentication#implementing-passport-jwt

  1. Use a synchronous extractor instead of an asynchronous one.

In the default passport-jwt extractor, we can see that it is synchronous rather than asynchronous. You can try removing the async keyword from your extractor or adding await when calling it.

https://github.com/mikenicholson/passport-jwt/blob/master/lib/extract_jwt.js, look for the fromAuthHeaderAsBearerToken function.

So either change your

const cookieExtractor = async (req) => {

to

const cookieExtractor = (req) => {

OR - add await when you call it

jwtFromRequest: await cookieExtractor(),
  1. Invoke the extractor instead of simply passing it.

In the example in the docs for the JwtStrategy constructor, they are invoking the extractor instead of merely passing it as you are doing.

jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),

Therefore, try invoking it in your JwtStrategy constructor

jwtFromRequest: cookieExtractor(),    // (again - take care of sync / async)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to achieve synchronicity with $.each function in jQuery?

I am working on an AJAX request that returns an array of objects upon success. The issue I am facing is with asynchronous looping using $.each. Inside the loop, there is a function that should execute for each object individually, but currently, it loops ...

Generate an image instantly from text using ajax when a key is pressed

Currently, I am immersed in a stencil project where my goal is to convert text into an image. In this particular task, there's a textbox that captures the user input on key up event. Once the user enters text, my aim is to display that text as an imag ...

Expo + tRPC: Oops! Looks like the application context couldn't be retrieved. Don't forget to wrap your App inside the `withTRPC` HoC for

I'm currently working on a straightforward tRPC server setup: // server.ts import { initTRPC } from "@trpc/server"; import { z } from "zod"; const t = initTRPC.create(); export const appRouter = t.router({ greeting: t.procedu ...

How can I use VueJS Cli to create a shared variable that is accessible across all pages and can be monitored for changes within a specific page?

Recently, I've delved into the world of VueJs and I'm encountering some issues with a project that I can't seem to resolve through online resources. I am trying to establish a variable that is common across all pages (month), and whenever t ...

Mastering the art of smooth transitions between three animation sequence states using three.js in the animate loop

I want to achieve smooth transitions for the three different wing flapping sequences within a short period of time. Currently, the transitions appear abrupt as they jump from one state to another. The wings have 3 distinct states: 1) On the ground, 2) Flyi ...

Invoke method from service on click in Angular 2

I'm facing an issue with a button component that should trigger a function on click event: <button pButton type="button" label="Add EchoBeacon" (click)="insertPoint()"> constructor(private mappaService: MappaService) {} ... insertPoint() { ...

Changing the entire contents of an array through innerHTML manipulation

I've encountered a strange issue with my Javascript code. I'm trying to create a table row using document.createElement("tr") and an array of cells like this: cell = new Array(3).fill(document.createElement("td")); However, when I populate the c ...

Bringing node.js variables into a database

I need help with the code I have written. It seems to be working but instead of updating the database with the actual result from a Node.js variable, it is inputting the string "$var". Can someone assist me with this issue? Thank you. var TOTP = require( ...

Chrome and Internet Explorer are not prompting to save passwords after the input type has been altered by a script

I've encountered an issue with a form that includes: <input type="password" id="password" /> I want to display some readable text temporarily, so I used the following code: $('#password').prop('type', 'text'); ...

How can I create a custom message in HTML using Angular directives within a marker popup using the angular-leaflet-directive?

I'm looking to add my own custom HTML markup with $scope event handlers to the message property of a Leaflet marker. Here's an example: App.controller('testController', ['$scope', "leafletEvents", '$compile', ' ...

Troubleshooting: Issue with AngularJS Image onload directive - "this" reference not functioning properly?

I have a custom directive that looks like this: .directive('ngImageOnLoad', function () { return { restrict: 'A', link: function(scope, element, attrs) { element.bind('load', function() { ...

Irreparable Glitches in Mocha

While developing a blogging platform, everything ran smoothly when tested on a web server. However, I encountered some issues when trying to write unit tests using Mocha and Should.js. Surprisingly, errors kept popping up where they shouldn't have bee ...

To activate the speech synthesis feature with a message, you must click a button for it to work

The issue with the code snippet below is that the function "window.speechSynthesis.speak(msg)" does not produce any sound output unless the button is clicked first. Only after clicking the button, the "Hello" message works as intended. Any attempts to call ...

Refreshing a component within a React application

I am new to React and I am experimenting with creating a simple program where you can select a name from a list, which will then display a proverb. I have created an event handler called handleAuthorClick and used setState to update the text accordingly, b ...

Loading SVGs on the fly with Vue3 and Vite

Currently, I am in the process of transitioning my Vue2/Webpack application to Vue3/Vite. Here's an example of what works in Vue2/Webpack: <div v-html="require('!!html-loader!../../assets/icons/' + this.icon + '.svg')" ...

JavaScript doesn't seem to be functioning properly within PHP

I have implemented the supersized jQuery script to incorporate sliding backgrounds on my WordPress page. Now, I aim to create unique slides for different sites and require a PHP if request. This is my code: <?php if ( is_page(array('Restaurant&a ...

Vue element fails to appear on the screen

As a newcomer to Vue.js and web development in general, I decided to dive into the vuejs guide. Something puzzled me - when creating a vue component using Vue.component(NameOfComponent, {...}) and inserting it into my HTML as <NameOfComponent></ ...

"Implement a feature that allows for infinite scrolling triggered by the height of

Looking for a unique solution to implement a load more or infinite scroll button that adjusts based on the height of a div. Imagine having a div with a height of 500px and content inside totaling 1000px. How can we display only the initial 500px of the div ...

Issue: the size of the requested entity is too large

Encountering an error message while using express: Error: request entity too large at module.exports (/Users/michaeljames/Documents/Projects/Proj/mean/node_modules/express/node_modules/connect/node_modules/raw-body/index.js:16:15) at json (/Users/ ...

Capturing AJAX responses within a Chrome Extension

We are currently in the process of developing a Chrome extension to enhance an existing system by simplifying various tasks. This extension will heavily utilize AJAX technology, making it more efficient compared to web scraping or manually triggering even ...