Error when encoding and decoding jwt on next-auth

4.8k views Asked by At

On next-auth when just using the callbacks object https://next-auth.js.org/configuration/callbacks everything works fine, I can log in with google. and a jwt gets correctly generated.

  callbacks: {
    async jwt({ token }) {
      console.log("token callback jwt: ", token)
      token.userRole = "admin"
      return token
    },
    async session(session) {
      console.log("session callback: ", session)
      const encodedToken = await jwt.sign(session.session.user, jwtSecret.key, {
        algorithm: jwtSecret.type,
      })

      session.token = encodedToken
      return session
    },
  },

But my end goal is to use next-auth jwt with hasura and for that I need to encode some more information to my jwt

I encode that information inside the jwt object https://next-auth.js.org/configuration/options#jwt

this is my jwt object with encode and decode logic

  jwt: {
    encode: async ({ token }) => {
      const tokenContents = {
        id: token.id,
        name: token.name,
        email: token.email,
        picture: token.picture,
        "https://hasura.io/jwt/claims": {
          "x-hasura-allowed-roles": ["admin", "user"],
          "x-hasura-default-role": "user",
          "x-hasura-role": "user",
          "x-hasura-user-id": token.id,
        },
        iat: Date.now() / 1000,
        exp: Math.floor(Date.now() / 1000) + 24 * 60 * 60,
        sub: token.id,
      }
      const encodedToken = jwt.sign(tokenContents, jwtSecret.key, {
        algorithm: jwtSecret.type,
      })
      return encodedToken
    },
    decode: async ({ token }) => {
      const decodedToken = jwt.verify(token, jwtSecret.key, {
        algorithms: jwtSecret.type,
      })
      console.log("decodedToken: ", decodedToken)
      return decodedToken
    },
  },

when I try to log in with my new encode decode logic, I receive next error that comes from node_modules > openid-client > lib > client.js

https://next-auth.js.org/errors#oauth_callback_error checks.state argument is missing {
  error: {
    message: 'checks.state argument is missing',
    stack: 'TypeError: checks.state argument is missing\n' +
      '    at Client.callback (/var/task/node_modules/openid-client/lib/client.js:385:13)\n' +
      '    at oAuthCallback (/var/task/node_modules/next-auth/core/lib/oauth/callback.js:112:29)\n' +
      '    at processTicksAndRejections (internal/process/task_queues.js:95:5)\n' +
      '    at async Object.callback (/var/task/node_modules/next-auth/core/routes/callback.js:50:11)\n' +
      '    at async NextAuthHandler (/var/task/node_modules/next-auth/core/index.js:139:28)\n' +
      '    at async NextAuthNextHandler (/var/task/node_modules/next-auth/next/index.js:21:19)\n' +
      '    at async /var/task/node_modules/next-auth/next/index.js:57:32\n' +
      '    at async Object.apiResolver (/var/task/node_modules/next/dist/server/api-utils/node.js:182:9)\n' +
      '    at async NextNodeServer.runApi (/var/task/node_modules/next/dist/server/next-server.js:386:9)\n' +
      '    at async Object.fn (/var/task/node_modules/next/dist/server/base-server.js:488:37)',
    name: 'TypeError'
  },
  providerId: 'google',
  message: 'checks.state argument is missing'
}

Just for curiosity I went to the client.js file where the code is breaking and this is the code inside, it has a if !checks.state throw new TypeError

if (params.state && !checks.state) {
  throw new TypeError('checks.state argument is missing');
}

It looks almost obvious that I need to add that checks.state to my encode logic but I don't know how, also I don't know that that checks.state object represents

1

There are 1 answers

0
Israel Ortiz Cortés On

Alright while I was writing this I found a piece of code that fixed the issue. in the issues page from next-auth https://github.com/nextauthjs/next-auth/issues/3251

someone says that if you add

GoogleProvider({
  clientId: process.env.GOOGLE_ID,
  clientSecret: process.env.GOOGLE_SECRET,
  checks: console.log(),
}),

to your GoogleProvider config it works, and it worked! why? I still don't know but still sharing because this can save days of research to someone else.

update, from the issues link I shared, I also tried

GoogleProvider({
  clientId: process.env.GOOGLE_ID,
  clientSecret: process.env.GOOGLE_SECRET,
  checks: "both",
}),

and not only works but console logs the decoded token.