Next-auth: is it possible to access the raw JWT?

Created on 24 Jun 2020  路  14Comments  路  Source: nextauthjs/next-auth

Please refer to the documentation, the example project and existing issues before creating a new issue.

Your question
Please please _please_ forgive me if I've overlooked this in the docs, but i cannot seem to find it: is it possible to access the raw jwt/idtoken (i.e. the string, not the decryped object)

What are you trying to do
I need to pass the token to a separate api.

Documentation feedback
Documentation refers to searching through online documentation, code comments and issue history. The example project refers to next-auth-example.

  • [x] Found the documentation helpful
  • [ ] Found documentation but was incomplete
  • [x] Could not find relevant documentation
  • [x] Found the example project helpful
  • [ ] Did not find the example project helpful
question

Most helpful comment

Hi @iaincollins
First, thanks for this project and being a great maintainer, love how thoroughly you address all the things here!
I just need to get the raw token, we verify it on the GraphQL backend anyway. Is the getJwt() somewhere documented? I can't find anything on it. This should be sufficient for my use case.
And when is v3 dropping?

PS: I want to get the JWT on the _client side_, any idea how I can grab it there?

All 14 comments

Thanks for the really clear question!

The short answer is there isn't a simple function for that. The conversation in #305 is a bit relevant though.

Under consideration

I am thinking of doing one of two things to help in this use case:

  • Supporting jwt: { encrypt: false } as an option (where JWT would be signed, but not encrypted)

    This is how JSON Web Tokens are most typically used, but NextAuth.js tries to go further by default, so that it's safe to also store private data that needs to be secret from the user in the JWT (based on an assumption that people may do that anyway without realising it's a bad idea if a token isn't encrypted).

  • Supporting returning the raw JWT (pre-verification) in the helper functions / callbacks

I'm interested in feedback regarding which of these (if either) is most useful (or not).

Workaround

Right now, you would have to write something like this:

import jwt from 'jsonwebtoken'
import CryptoJS from 'crypto-js'

export default async (req, res) =>  {
  // Get token from cookie (names differ on local vs production)
  const secureCookieName = '__Secure-next-auth.session-token'
  const insecureCookieName = 'next-auth.session-token'
  const encryptedToken = req.cookies[secureCookieName] || req.cookies[insecureCookieName]

  // Decrypt using secret and return as string
  const decryptedBytes = CryptoJS.AES.decrypt(encryptedToken, process.env.SECRET)
  const decryptedToken = decryptedBytes.toString(CryptoJS.enc.Utf8)

  // Still signed but not encrypted (this is what you want)
  console.log(decryptedToken)

  // If you wanted to verify it and get the object back, you would do this step too
  const verifiedToken = jwt.verify(decryptedToken, process.env.SECRET)

  res.end()
}

Holy cow thanks for the speedy response and workaround!

For my use case, the latter would be ideal! In most cases I _want_ the decrypted token. But there are instances when I need to forward the auth token to another service that uses the same Auth0 tenant. Being able to insert the raw token into the session using callbacks (or something), so that I can use it in external API calls would be ideal. (basically I'm looking to do something like

fetch('https://some-external-thing.example', { headers: { Authorization: `Bearer: ${session.idToken}` } })

I think this workaround can get me there, if I proxy the api calls that need the token through Next (which is maybe the right thing to do anyway?).

I watch carefully for this project and I think you did a great job! I will try to put my few cents to this conversation to raise some of my concerns.

With the current implementation having JWT: { encrypt: false } should not be the option for security reasons.

What do I mean by current implementation:
Current authorization uses JWT mostly as a way to store information, rather than the original goal it was created. JWT acts like a session.

Even if JWT is just a standard of how to keep, encrypt certain information, Authorization systems that use it, usually operate with two different types of tokens:

  • Access token
  • Refresh token

An access token has a very short lifetime and can't be invalidated. Refresh tokens have a long lifetime and could be invalidated. With Access token you send information to any other third party service that could verify signature, grant access to you for certain features. So basically this issue and the one I created before is the general purpose of JWT.

Access token and refresh token both stored on the client-side. The best way to store them inside variables available within a specific scope rather than adding them too cookies, session or local storage.

Naming issue
I still think that there is a naming issue in place inside the project. JWT is encrypted by the standard. But in the current implementation, we encrypt already encrypted JWT. That's why I proposed to change the naming and use jwtPayload for places where we want to know the content of the JWT, jwtToken should represent the token as it is by the standard, but I don't know how to name that thing that currently named as JWT :) probably just call it sessionToken or something like this, because it's not JWT.

Supporting returning the raw JWT
It will help to resolve some issues, but not everything. From the server-side, we will be able to start passing that token further, but we won't be able to use it from the client-side since it will be very insecure with the current implementation described above.

I still find JWT based authorization very complex from the concept perspective so I could be wrong in some statements, however, based on my understanding and experience currently, it's too early to say that next-auth supports JWT.

@lnikell I've marked that as off-topic as I think it will spark a discussion that will detract from the issue @drewwyatt has raised and trying to keep things on-topic to help the original poster as possible, please feel free to raise that feedback as separate issue!

I think this workaround can get me there, if I proxy the api calls that need the token through Next (which is maybe the right thing to do anyway?).

Yeah that is usually a good approach!

Alternative

_(Not recommend, but for completeness!)_

If you know you don't have private data (e.g. private keys, session tokens, etc) in the JSON Web Token you can always define your own routines to encode/decode it and remove the encryption - then the token could be readable by other services on the same domain. (The encrypt: false option would make this simpler if we add that.)

However, to share the built in token with other services would also need to modify the metadata on the cookie, so that it's readable by other services on the same domain, and that opens up some potential security problems. So I would suggest probably not doing that, unless there are technical constraints or you know you can trust all hosts on a domain.

Proxying requests via a simple function that does the checking is generally the best approach (signed or unsigned). You can even decode and verify JWT from things like nginx, which is super flexible (usually not possible if they are also encrypted though).

Follow up

I've expanded the info on #224 to address the points that have come up in this issue.

That is probably the best place to track this.

I think it is likely we will do something like either add the encrypt: false and/or provide a way of returning the raw token from the getJwt() function.

This would be great - I would like the option. This library is a god-send for me as I have been faffing with self-authored auth using passport and also trying auth0, neither quite what I need. This is perfect but I do use the JWT with my APIs and would like the option to access the raw token. Thanks for all your hard work.

I would absolutely love to have a helper that returns the raw JWT, so we can feed it into Apollo for GraphQL authorization.

@tmaximini Hi there! To clarify, do you need the signed (and/or encrypted, if encryption enabled) JWT (e.g. the raw Base64 encoded cookie) or do you just need the JSON payload (and to know it's verified)?

The getToken() helper in v3 (an improved version of the getJwt() method from v2, that works the same in most cases) should handle returning the decoded payload in JSON for anyone that needs it. It ensures the token has a valid signature (and handles decryption, if enabled).

If it's also helpful to be able get the token without decrypting / verifying it first I'm very happy to add an option to that function.

Hi @iaincollins
First, thanks for this project and being a great maintainer, love how thoroughly you address all the things here!
I just need to get the raw token, we verify it on the GraphQL backend anyway. Is the getJwt() somewhere documented? I can't find anything on it. This should be sufficient for my use case.
And when is v3 dropping?

PS: I want to get the JWT on the _client side_, any idea how I can grab it there?

I too would like to know what's the recommended way (in v3) to get and use a raw (string version of) JWT on the client side. In my understanding, that way next-auth can be used with other graphql backend frameworks/services which accept JWTs in request headers.

I often come across documentations that say you can integrate custom JWT, for example with Strapi, with Hasura, and with MongoDB Atlas and I thought these can be somehow done in combination with next-auth. (Apologies in advance if I'm misunderstanding something... Should I just wait?)

Sorry if this is a #metoo but I assume I have also been hit by this. I would like to forward the JWT to a backend API so it can verify the JWT is legit.

I have two things I want to achieve:
1) When the user signs in/session renews, to call the API with the JWT (which is validated against the initial provider) and the API will return the user's roles/permissions to be assigned client side (for managing what should be shown on UI)
2) Also when the user interacts with the UI, the JWT should be sent as part of API calls which will be checked again (against the provider) to see if the user can perform such a command.

Is this possible today or is this issue related? From what I read it seems like it's not possible 馃

The raw JSON Web Token is not exposed directly on the client side, this is important as it would make it easier for third party scripts to do session hijacking - this is an important security feature for session tokens, as they should be "HTTP only" (meaning only readable server side).

The session callback can be used to expose data you need to securely from the JSON Web Token to the session (via useSession() or getSession()) where the client can access it.

To pass a token through to an API route securely, the best way to do that is to create an API route that reads in the token and uses the getToken() method with the option { raw: true } to get the raw JWT and then pass it through to the API you are calling (and return the response from it from your API endpoint).

You can find an example of using the getToken() method in the example project:
https://github.com/nextauthjs/next-auth-example/blob/main/pages/api/examples/jwt.js

To get the raw token, you would call it like this:
const rawToken = await jwt.getToken({ req, secret, raw: true })

The getToken() method (which replaced the getJwt() method but is very similar, just improved) is documented here:
https://next-auth.js.org/configuration/options#jwt-helper

It's not the most obvious place for the documentation for this function but it lists the supported options. It could do with its own documentation page and some examples of how to use it.

Thanks, though I can see this is just the NextAuth JWT.

So I tried to use the jwt callback to copy over the accessToken from the provider (to forward to the API later) into the JWT and I am assuming the accessToken is too large since now I get no JWT set at all.

        jwt: async (token, user, account, profile, isNewUser) => {
            if (profile) {
               // return Promise.resolve({...token, accessToken: account.accessToken}); // No session token cookie is set
               return Promise.resolve({...token, accessToken: "this works"});
            }
            return Promise.resolve(token);
        }

I'm not seeing any errors from this either. I believe this is probably due to the large amount of groups returned (from Azure AD) and is hitting the upper limit of setting of the cookies (mentioned in your FAQ) so I should probably work around this by separating the call 馃槃

Hopefully that's the correct approach to take.

@carlreid Yes, there is a practical browser limit on how big cookies can be (~4096 bytes in total for any domain) so best to keep the JWT as small as possible (this isn't a limit in NextAuth.js and we can't even detect when it happens as it's dependant on other cookies, so there isn't much we can do about it).

If you need to persist other data (e.g. from Azure AD or AWS Cognito) the jwt callback can be used to persist this in something like a key/value datastore at sign in, and an API route can be used to fetch the data from the datastore, by using something like a User ID (or just an ID that is returned from the key/value store that is then persisted in the JWT).

Generating an access token using a UUID and storing in the JWT while also saving it as the key in the key/value store is a great way to do this, as the token is signed no-one can tamper with it.

You could even add the data to the session object using the session callback, but creating a dedicated API endpoint to read the token (e.g. /api/user/profile) and then fetch the data as needed is probably best, as it keeps the session check smaller and faster (and will hit the database less).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

iaincollins picture iaincollins  路  3Comments

dmi3y picture dmi3y  路  3Comments

iaincollins picture iaincollins  路  3Comments

simonbbyrne picture simonbbyrne  路  3Comments

jimmiejackson414 picture jimmiejackson414  路  3Comments