Next-auth: Not getting a JWT on login with the Example App

Created on 28 Sep 2020  路  19Comments  路  Source: nextauthjs/next-auth

Hi,

I'm not getting a JWT via a Google login through the next-auth-example app running locally.

Using the online next-auth-example app with my Google account, I can see in the console that I'm getting a JWT. When I download the same sample app, add my Google credentials and yarn dev, I do not get a JWT using the same Google account to log in. My understanding is that JWT should be used when either no db is set up or JWT: true is set in the api/auth/[...next-auth].js settings page. Both of these are true-- no db has been set up and jwt is set to true, but I'm not getting a JWT and the iframe in the sample app shows null, while the session iframe shows the correct, logged in session information. I'm sure I'm missing something simple but I've spent hours looking at this today and I can't figure it out.

I'm trying to use this JWT to log into MongoDB Realm.

If someone could point me in the right direction, I'd be very grateful.

Thanks.

Brad

help wanted question stale

All 19 comments

A little more testing this morning. I cloned the example app from the Next-Auth repository and posted to my Vercel account, added my Google credentials and the NEXTAUTH_URL env variables ("mine") and then ran it side-by-side with the official Next-Auth online demo at https://next-auth-example.now.sh/api-example ("official").

I inspected the Network tabs for both versions side-by-side and found that my version gets a 304 Not Modified status to the jwt file GET request and it uses the cached version. I removed the application from my account at https://myaccount.google.com/permissions and added the authorizationURL: as suggested at https://next-auth.js.org/providers/google. Indeed, I'm being forced to grant permission on successive attempts, but I'm still getting 304s.

Another difference I noticed is that the official version has an entry for a script at

/_next/static/chunks/main-9a345281ff6f8bac3ce9.js

that does not show up in the Network tab on my version of the example app.

My version, cloned from https://github.com/nextauthjs/next-auth-example last night and published on Vercel this morning, is here:https://next-auth-vert.vercel.app/api-example

I also created another Vercel app with Github and Google credentials. I'm having the same problems with the Github login option-- I'm getting a status of 304/Not Modified instead of a JWT. session: {jwt: true} is set and no databases have been configured. If something else needs to be done to request and keep JWTs, perhaps the docs could be more explicit?

The new project is here--https://next-auth-test2.vercel.app/api-example

Below is a screenshot of the Network tabs of both the official version, on the left, and my first version, where the entry for /_next/static/chunks/main-9a345281ff6f8bac3ce9.js is absent.
Screen Shot 2020-09-28 at 8 15 18 AM

raw: true

I added raw:true to the following:
const token = await.jwt.getToken({ req, secret, raw: true})

in api/examples/jwt.js to get the encoded jwt. Without raw:true, the jwt iframe shows 'null'. So, something has changed between the example code hosted at Vercel and the example code cloned from Github.

I found this in this thread:

https://github.com/nextauthjs/next-auth/issues/523#issue-670966915

Hi there,

I'm happy to confirm the example project website and the repo have the same codebase. - the codebase is deployed the website on commit.

I suspect the problem here is that the SECRET environment variable is not defined on the deployment.

If a shared secret is not configured, NextAuth.js generates one automatically (and in turns uses it to create JSON Web Keys for signing (and optionally for encrypting) the JWT.

The process isolation of Serverless means any other API routes will not be able to use it decode the JWT if they do not know the secret (but can pass the token through to NextAuth.js, which is why the session endpoint still works).

You can see how the example API reads the SECRET and passes it to the decode function as the argument secret:

import jwt from 'next-auth/jwt'

const secret = process.env.SECRET

export default async (req, res) => {
  const token = await jwt.getToken({ req, secret })
  res.send(JSON.stringify(token, null, 2))
}

Hi Iain,

Thanks for your quick response and patience while I get up to speed on JWT. I have defined a completely random SECRET comprised of alphanumeric characters
Screen Shot 2020-09-29 at 6 27 21 PM
and my code is exactly as you've listed above, yet, I get null in the jwt iframe, as shown in the attached screenshot.

However, when I add raw: true, I do get the hashed token. Running the contents of this jwt iframe through the debugger at https://jwt.io/#debugger-io reveals the correct decoded data.

Screen Shot 2020-09-29 at 6 38 21 PM

Ugh! If the app is consuming the same SECRET in production I have no idea what is going on there.

The error handling isn't great currently (that's a known issue) so something bad is probably happening but not being surfaced.

For now I can only suggest maybe hacking around in node_modules/next-auth/dist/lib/jwt.js and adding some console.log entries (or using debugger) to try and work out why it's returning null from getToken.

This is working locally for me and am not able to reproduce the problem with the example repo (or on other projects).

Well, thanks for confirming I'm not a crazy dum-dum. If I find the source of the problem, I'll report back. I need to dig around in encoding, anyway, to change it to something MongoDB's JWT authentication accepts (HS256 or RS256).

@b-gibbs @iaincollins I have exactly the same problem. I only get the token with raw:true, else it is null. Tried everything... @b-gibbs any news on the solution? Best Regards

Paul, can you please look at your network activity as you sign in? The first screenshot I posted shows network activity upon sign in, with the official posted example on the left and my clone of that on the right. The line that is highlighted on the left is missing in my cloned version. I have a feeling it鈥檚 key to what鈥檚 happening. Also, please check to see if you鈥檙e getting 304 statuses for your jwt entries on the network tab.

Iain, do these differences give you any clues as to what might be happening?

I just cloned https://github.com/b-gibbs/next-auth-test2.git, added a .env file with NEXTAUTH_URL, SECRET, client ID, client secret for a provider ran it and it works for me - I can see the JWT on the API page.

If I create a .env (or .env.local) without a secret defined I get exactly the behaviour described (as anticipated), but if it's defined, it works exactly as expected.

In the screenshot, you only have NEXTAUTH_URL and SECRET values configured, if that's the case, how are you testing actually signing in locally?

I added several carriage returns to push my GOOGLE_ID and GOOGLE_SECRET down behind the Firefox image.

I've tried specifically with Google as a provider, and again works okay from that branch for me. I also installed this repo fresh with Yarn rather than NPM (as Yarn making different determinations for dependencies can sometimes cause problems in edge cases) and specifically with Chrome and Firefox (as per screenshot) works for me with no issues. I even tried using the same secret as in the screenshot above.

Debug NextAuth JWT Report

I deployed the branch provided to https://b-gibbs-next-auth-test2.vercel.app and configured the instance it in Vercel (with GitHub as a new provider created specifically to test this site, as shown below) and that also works.

Debug NextAuth JWT 2

This behaviour is expected (and happens) if a shared secret is not configured, but beyond cannot currently be replicated on Mac OS X, Linux, Windows or on x86 or ARM, or on Vercel, with Chrome, Firefox, Safari or Edge, using the example repo provided.

I suspect the only way you are going to get to the bottom of this is to debug what's going on inside the library when running locally.
The fu
You can do this either by editing node_modules/next-auth/dist/lib/jwt.js locally or copy and paste the code into your own project (the only thing you'd need to stub out are the logger lines and it's only referenced in the import and on two other lines) and debug the getToken function till whatever is causing the errors surfaces.

https://github.com/nextauthjs/next-auth/blob/main/src/lib/jwt.js

We actually no longer minify the transpiled JavaScript in NextAuth.js on publish, to make this sort of thing easier (as the saving in file size is minimal as the library is delivered compressed anyway), so it should be fairly straightforward to add some console.log lines to that file inside the getToken method and see exactly what is going on.

The function is only about 30 lines long so it should be quick to track down the underlying cause.

Thanks for the work you鈥檙e putting into this, Iain. Truly appreciated. I will try this tomorrow.

I鈥檒l also add that I鈥檓 only really having problems with the iframe. I can reliably get the token / user info with a console.log.

Also, the token was appearing from time-to-time today in the jwt iframe. I tried to track down what鈥檚 causing it to work or not work, but haven鈥檛 been able to. I鈥檓 on Mac, using Firefox Developer and Chrome and yarn.

I鈥檓 hopeful the code you posted will resolve the issue and I鈥檒l be able to post the reason why and the fix tomorrow.

I've been noticing that when I get a null value in the jwt iframe, I'm also getting a [next-auth][warn][jwt_auto_generated_signing_key] warning in the terminal. The link to the webpage explaining the warning is broken, but my understanding is that this relates to the secret in .env.local, and that if a secret is defined, I should not be seeing this warning. However, I do have a secret defined and I'm still getting the warning. See the attached screenshot.

Screen Shot 2020-10-01 at 6 43 29 PM

@b-gibbs @iaincollins What I am trying to do is that I have an api route in api folder where I try to get jwt token.

.env.local

SECRET= test
// JWT_SIGNING_PRIVATE_KEY created with command jose newkey -s 256 -t oct -a HS512
JWT_SIGNING_PRIVATE_KEY={"kty":"oct","kid":"Q4cNvjs0M2e3Sm-2eobQLS22HNLv4zVIU41f8Qv8iY4","alg":"HS512","k":"Q_Wuj9MU__yi7Dhaa1UHLSPO3ke-mVdjzgG7Rzpj-oY"} 

My route in /api folder:

const secret = process.env.JWT_SIGNING_PRIVATE_KEY; // Same error if I use process.env.SECRET;

export default async (req, res) => {
  const token = await jwt.getToken({ req, secret });
  console.log("token is", token);
  ...
}

I always get that token is null except when I add raw:true, then I get the raw token.

The relevant part of my config looks like the following:

 // secret: process.env.JWT_SIGNING_PRIVATE_KEY, ** secret is commented out.** 

  session: {
    jwt: true,
  },
  jwt: {
    signingKey: process.env.JWT_SIGNING_PRIVATE_KEY,
  },

So if I use process.env.JWT_SIGNING_PRIVATE_KEY or process.env.SECRET for jwt secret in api route it is still null. Any ideas what to do?
Best Regards

I spent some hours looking at this in anger this weekend and I think I've cracked it or at least some part of it.

I'm signing, but not encrypting. I'm using the token I get from Next-Auth to authorize and authenticate users on a backend API server running Apollo Server on top of Express. This is fine, as long as I use the same secret on the Next app server and the API server.

If I just provide a secret in [...nextauth].js, jwt auto-generates a signing key using futoinHkdf, which hashes the secret, meaning the secret on Apollo server no longer works. Coincidentally, it also means that the debugger at jwt.io won't work with the unhashed secret.

If I pass my own secret + signingKey to encoding and decoding, it works with some further modifications:

  1. secret, must be added to this line
    var _signingKey = signingKey ? _jose.default.JWK.asKey(JSON.parse(signingKey)) : getDerivedSigningKey(secret);

to change it to this
var _signingKey = signingKey ? _jose.default.JWK.asKey(secret, JSON.parse(signingKey)) : getDerivedSigningKey(secret);

in order to conform to joses's secret key import signature JWK.asKey(secret[, options]) Secret Key Import

  1. When decode() is called at the end of the getToken() method, getToken() but doesn't have and can't pass the signingKey arguments that are passed to encode() and decode() when called from [...nextauth].js so, that decode() fails silently and null is displayed in the jwt iframe.

@iaincollins, am I misusing or misunderstanding something about the way this is intended to be used? If not, will you please add secret as an argument to JWK.asKey() in both encode() and decode() and make it possible to pass a custom signingKey to getToken()?

Thanks.
Brad

Hi there! It looks like this issue hasn't had any activity for a while. It will be closed if no further activity occurs. If you think your issue is still relevant, feel free to comment on it to keep it open. (Read more at #912) Thanks!

Hi there! It looks like this issue hasn't had any activity for a while. To keep things tidy, I am going to close this issue for now. If you think your issue is still relevant, just leave a comment and I will reopen it. (Read more at #912) Thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ryanbahan picture ryanbahan  路  3Comments

Xetera picture Xetera  路  3Comments

readywater picture readywater  路  3Comments

ghoshnirmalya picture ghoshnirmalya  路  3Comments

simonbbyrne picture simonbbyrne  路  3Comments