vue.jsjwtnuxt3.jsmsw

How to use 'jsonwebtoken' library to generate a JWT token in Nuxt 3?


I'm using the mswjs library for mocking APIs in a Nuxt 3 (3.0.0-rc.11) project; Everything is working fine until I try to import the 'jsonwebtoken' library to generate a JWT access token to mock the login API call.

Here is the login request handler:

//file path: @/mocks/auth.js
import {rest} from 'msw'
import { addBaseUrl, makeResponse } from './helpers'
import jwt from 'jsonwebtoken'

export default [
   rest.post(addBaseUrl('/api/auth/login/'), (req, res, ctx) => {
     const json = {
       access: jwt.sign(
         {test: 'test'},
         'secret',
         {expiresIn: 600}       )
     }
     return makeResponse({res, ctx, status: 200, delay: 1000, json})
  }),
]

The problem is that when I try to import jwt, it throws this error and everything stops working:

util.js:109 Uncaught TypeError: Cannot read properties of undefined (reading 'NODE_DEBUG')
    at node_modules/util/util.js (util.js:109:17)
    at __require (chunk-7NEK6ARH.js?v=bb8c835c:9:50)
    at node_modules/jws/lib/data-stream.js (data-stream.js:4:12)
    at __require (chunk-7NEK6ARH.js?v=bb8c835c:9:50)
    at node_modules/jws/lib/sign-stream.js (sign-stream.js:3:18)
    at __require (chunk-7NEK6ARH.js?v=bb8c835c:9:50)
    at node_modules/jws/index.js (index.js:2:18)
    at __require (chunk-7NEK6ARH.js?v=bb8c835c:9:50)
    at node_modules/jsonwebtoken/decode.js (decode.js:1:11)
    at __require (chunk-7NEK6ARH.js?v=bb8c835c:9:50)

The problem is caused by jws library which is a dependency of jsonwebtoken library. I have no idea about how to fix this issue, and I would really appreciate any help.

Here is my package.json file:

{
  "private": true,
  "scripts": {
    "start": "nuxt start",
    "build": "nuxt build",
    "dev": "nuxt dev --https --ssl-cert localhost.pem --ssl-key localhost-key.pem",
    "generate": "nuxt generate",
    "preview": "nuxt preview"
  },
  "devDependencies": {
    "@iconify/vue": "^4.0.0",
    "autoprefixer": "^10.4.8",
    "msw": "^0.47.3",
    "nuxt": "3.0.0-rc.11",
    "postcss": "^8.4.16",
    "tailwindcss": "^3.1.8"
  },
  "dependencies": {
    "@vue-leaflet/vue-leaflet": "^0.6.1",
    "@vuelidate/core": "^2.0.0-alpha.44",
    "@vuelidate/validators": "^2.0.0-alpha.31",
    "dayjs": "^1.11.5",
    "jsonwebtoken": "^8.5.1",
    "jwt-decode": "^3.1.2",
    "leaflet": "^1.9.1",
    "vue-google-charts": "^1.1.0",
    "vue3-carousel": "^0.1.46"
  },
  "packageManager": "yarn@3.2.3",
  "msw": {
    "workerDirectory": "public"
  }
}

Solution

  • Since jsonwebtoken seems to use node-jws, I think, that it will not work, because mswjs (mock service worker JS) runs in the browser and thus not on node.

    I am currently looking for a solution to the same problem though as I'd like to keep using MSWjs for mocking my backend and at the same time creating valid JWTs (in the browser, because the mock service worker script runs in the browser, not in a backend).

    I am going to try https://www.npmjs.com/package/jose in combination with MSWjs.

    Edit: I have tried it out and it seems to work.

    I have generated a publicKey for verifying and a privateKey for signing JWTs in my browser.ts like so:

    export const { publicKey, privateKey } = await jose.generateKeyPair('ES256')
    

    And used in my auth-handlers.ts, which I included in the handlers.ts like so:

    import { privateKey } from './browser'
    ...
    async function createJwt(foundUser: UserData | undefined): Promise<string> {
        return new jose.SignJWT({ 'urn:example:claim': true })
            .setProtectedHeader({ alg: 'ES256' })
            .setIssuedAt()
            .setIssuer('urn:example:issuer')
            .setAudience('urn:example:audience')
            .setExpirationTime('2h')
            .sign(privateKey)
    }
    

    It's important to keep the publicKey available for all of your handlers, because you'll use it to verify the JWT you get in the request header and perhaps use claims if need be.

    Furthermore I avoided redirecting to a login screen and instead sent the {email: ..., password: ...} body as a POST request to /auth/login in case no cookie was found when processing a supposedly protected endpoint.

    In reality you would redirect the user to a login form and - once completed - redirect the user back to the original URL from what I understand.

    Finally I used jose to generate and sign the JWT and appended the result to the header of the auth-handler response using ctx.cookie('Authorization', 'Bearer' + jwt). Since it redirected back to the URL that it was called from (/api/user/login -> /api/auth/login -> cookie appeneded to response -> /api/user/login), I was able to process the login and return the Authorization: Bearer ... header, which I then could use for my next call.

    I still need to figure out if I should use refresh tokens and how to do so, but I hope this helps you getting started.