Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Does Next Auth handle automatic JWT refresh? #5652

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
iWanheda opened this issue Oct 26, 2022 · 28 comments
Closed

Does Next Auth handle automatic JWT refresh? #5652

iWanheda opened this issue Oct 26, 2022 · 28 comments
Labels
help-needed The maintainer needs help due to time constraint/missing knowledge stale Did not receive any activity for 60 days

Comments

@iWanheda
Copy link

Question πŸ’¬

Not saying my provider's token, my own generated JWT token with:
image

It doesn't seem to be handling it automatically, since I wait 30s and then get logged out

(I'm using Discord as auth provider)

How to reproduce β˜•οΈ

None

Contributing πŸ™ŒπŸ½

No, I am afraid I cannot help regarding this

@iWanheda iWanheda added the question Ask how to do something or how something works label Oct 26, 2022
@noxify
Copy link
Contributor

noxify commented Oct 26, 2022

No, but there is a tutorial which explains how to implement it: https://next-auth.js.org/tutorials/refresh-token-rotation

@lantaozi

This comment was marked as spam.

@agusterodin
Copy link

I don't believe this library supports out-of-the-box token refresh. I believe refetchInterval lets you set how often your client reaches out to Next Auth backend. It is still up to you to handle token refresh logic on the Next Auth backend.

I had to implement refresh tokens myself for Google identity platform and it has weird issues. It doesn't help that Google tokens in particular are so short lived and expire quickly (probably intentional design by Google).

In my opinion, authentication is not a solved problem (and is a damn nightmare) until refresh tokens fully seamlessly work. Would love to see that in a future version of this library πŸ™

Zero disrespect intended. Thank you to the maintainers of this awesome library.

@ThangHuuVu
Copy link
Member

We don't have out-of-the-box support yet, but we absolutely want to do this πŸ™Œ Things are a bit hectic at the moment for the core team, so PR is welcome!

@ThangHuuVu ThangHuuVu added help-needed The maintainer needs help due to time constraint/missing knowledge and removed question Ask how to do something or how something works labels Oct 29, 2022
@iWanheda
Copy link
Author

You've got a PR that is almost working from what I've seen, can you not spare a few mates from the team to actually implement it? I'm not hating just asking :)

@hichemfantar
Copy link

I def agree that JWT auth is not complete without a token refresh strategy. Would love to see some movement on this.
Love to the team working on this project πŸš€

@byanes
Copy link

byanes commented Nov 23, 2022

This would be amazing to have. Thank you for the amazing work!

@vwatel
Copy link

vwatel commented Dec 29, 2022

Hey guys,
Any update on this one? I'm also looking for refresh token strategy in order to have end-to-end integration using this great library.
Than you :)

@nreh
Copy link

nreh commented Jan 20, 2023

I also followed https://next-auth.js.org/tutorials/refresh-token-rotation to implement token refreshing

However, for some reason the re-fetch interval doesn't seem to line up with my token's expiration time, (my guess is that the token is acquired from some cache or cookies so it might already be close to expiration). As a result there is a small window between token expiration and refetching where the token is invalid for making API calls.

In addition, I assumed that the JWT callback function would fire every time useSession is called, as indicated by the documentation:

https://next-auth.js.org/configuration/callbacks#jwt-callback

Requests to /api/auth/signin, /api/auth/session and calls to getSession(), unstable_getServerSession(), useSession() will invoke this function, but only if you are using a JWT session.

However, I found this issue #4227 which indicates that this is not the case:

There is no session refetching by default, useSession uses what's in the cache. You can poll the session if you need to trigger the JWT callback.
https://next-auth.js.org/getting-started/client#refetch-interval
Another option is using getSession which always visits the backend: https://next-auth.js.org/getting-started/client#getsession

I'm trying to find a way to modify the useSession hook so that if the token is expired, it automatically requests a new one but I'm not sure how to override it nor do I know how to manually trigger a repoll of the token.

At this point I'm thinking of making the refetchInterval to a short timespan and making the token lifespan longer, but that seems like a very wasteful workaround.

@stale
Copy link

stale bot commented Mar 25, 2023

It looks like this issue did not receive any activity for 60 days. It will be closed in 7 days if no further activity occurs. If you think your issue is still relevant, commenting will keep it open. Thanks!

@stale stale bot added the stale Did not receive any activity for 60 days label Mar 25, 2023
@andrei-ivanov
Copy link

no activity doesn't mean it's not needed

@stale stale bot removed the stale Did not receive any activity for 60 days label Mar 25, 2023
@stale
Copy link

stale bot commented Jun 9, 2023

It looks like this issue did not receive any activity for 60 days. It will be closed in 7 days if no further activity occurs. If you think your issue is still relevant, commenting will keep it open. Thanks!

@stale stale bot added the stale Did not receive any activity for 60 days label Jun 9, 2023
@andrei-ivanov
Copy link

no activity doesn't mean it's not needed

@stale stale bot removed the stale Did not receive any activity for 60 days label Jun 10, 2023
@stale
Copy link

stale bot commented Aug 10, 2023

It looks like this issue did not receive any activity for 60 days. It will be closed in 7 days if no further activity occurs. If you think your issue is still relevant, commenting will keep it open. Thanks!

@stale stale bot added the stale Did not receive any activity for 60 days label Aug 10, 2023
@andrei-ivanov
Copy link

no activity doesn't mean it's not needed

@TheCarioca
Copy link

TheCarioca commented Aug 10, 2023

I have been trying to remove the expired tokens, according to this topic, but it is not re-populated. What I am trying to do now is checking it manually, changing the session callback. First I verify if the expires_at > Date.now() from the account stored in my mongodb and fetching to https://discord.com/api/v10/oauth2/token. However I am not able to get the token to use the grant_type: "authorization_code". By the way I am using the database strategy, but in case it helps, here is my attempt so far:

import NextAuth from "next-auth";
import DiscordProvider from "next-auth/providers/discord";
import clientPromise from "@lib/mongodb";
import { MongoDBAdapter } from "@next-auth/mongodb-adapter";
import { ObjectId } from "mongodb";

export const authOptions = {
  providers: [
    DiscordProvider({
      clientId: process.env.DISCORD_CLIENT_ID,
      clientSecret: process.env.DISCORD_CLIENT_SECRET,
      authorization: { params: { scope: "identify email guilds" } },
    }),
  ],
  adapter: MongoDBAdapter(clientPromise),
  secret: process.env.NEXTAUTH_SECRET,
  callbacks: {
    session: async ({ session, user }) => {
      const client = await clientPromise;
      const db = client.db();
      const { expires_at, refresh_token } = await db
        .collection("accounts")
        .findOne({
          userId: new ObjectId(user.id),
        });

      // if (expires_at > Date.now()) {
      if (expires_at < Date.now()) {
        try {
          const response = await fetch(
            "https://discord.com/api/v10/oauth2/token",
            {
              method: "POST",
              headers: {
                "Content-Type": "application/x-www-form-urlencoded",
              },
              body: new URLSearchParams({
                client_id: process.env.DISCORD_CLIENT_ID,
                client_secret: process.env.DISCORD_CLIENT_SECRET,
                grant_type: "authorization_code",
                refresh_token: refresh_token,
                redirect_uri: "http://localhost:3000/",
                scope: "identify email guilds",
              }),
            }
          );

          const data = await response.json();
          console.log(data);

          if (!response.ok) throw data;

          await db.collection("accounts").updateOne(
            { userId: new ObjectId(user.id) },
            {
              $set: {
                access_token: data.access_token,
                expires_at: Date.now() + data.expires_in * 1000,
                refresh_token: data.refresh_token || user.refreshToken,
              },
            },
            { upsert: false }
          );
        } catch (error) {
          console.error("Error refreshing access token", error);
        }
      }

      session.user.id = user.id;
      return session;
    },
  },
};
export default NextAuth(authOptions);

@stale stale bot removed the stale Did not receive any activity for 60 days label Aug 10, 2023
@KalleV
Copy link

KalleV commented Aug 10, 2023

I'm eager to see built-in support for refresh tokens too! Here's the relevant refresh token-related snippets for how I set it up with the Keycloak provider.

Helpers

const TOKEN_REFRESH_THRESHOLD_IN_SECONDS = 30;
const keycloakIssuer = process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER;

/**
 * Takes a token, and returns a new token with updated
 * `accessToken` and `accessTokenExpires`. If an error occurs,
 * returns the old token and an error property
 */
async function refreshAccessToken({refresh_token}: {refresh_token: string}) {
  const params = new URLSearchParams({
    client_id: process.env.KEYCLOAK_CLIENT_ID as string,
    grant_type: 'refresh_token',
    refresh_token,
  });

  const url = `${keycloakIssuer}/protocol/openid-connect/token`;

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: params,
  });

  const refreshedTokens: TokenSet = await response.json();

  if (!response.ok) {
    throw refreshedTokens;
  }

  return refreshedTokens;
}

// ...

/**
 * Checks if the access token is expired or not. Goal is try to refresh the token before it expires.
 *
 * Simplified version of this function:
 * https://github.com/keycloak/keycloak/blob/67c64c37df4c33a90f4db1873620da06fb751cd4/js/libs/keycloak-js/src/keycloak.js#L575
 *
 * @param minValidityInSeconds The minimum validity time in seconds for the access token.
 * @returns A boolean indicating whether the access token is expired or not.
 */
const isTokenExpired = (minValidityInSeconds: number, expiresAt: number) => {
  return Date.now() + minValidityInSeconds * 1000 > expiresAt * 1000;
};

Auth Options

Session Callback

{
  // other options
  async session({ session, user }) {
    /* Retrieve "userAccount" from adapter */

    if (
      userAccount &&
      isTokenExpired(TOKEN_REFRESH_THRESHOLD_IN_SECONDS, userAccount.expires_at!)
    ) {
      try {
        const refreshedTokens = await refreshAccessToken({
          refresh_token: userAccount?.refresh_token as string,
        });

        await myAdapter.linkAccount({
          ...userAccount,
          access_token: refreshedTokens.access_token,
          expires_at: Math.floor(
            Date.now() / 1000 + (refreshedTokens.expires_in as number),
          ),
          refresh_token:
            refreshedTokens.refresh_token ?? userAccount.refresh_token,
        });

        session.accessToken = refreshedTokens.access_token as string;
        session.idToken = refreshedTokens.id_token as string;
      } catch (error) {
        // The error property will be used client-side to handle the refresh token error
        session.error = 'RefreshAccessTokenError';
        return session;
      }
    } else {
      session.accessToken = userAccount?.access_token as string;
      session.idToken = userAccount?.id_token as string;
    }

    /* Assign attributes to session then return it for the frontend */

    return session;
  }
}

@TheCarioca
Copy link

@KalleV just 30 seconds of threshold is enough? I have no idea :)

@stale
Copy link

stale bot commented Oct 15, 2023

It looks like this issue did not receive any activity for 60 days. It will be closed in 7 days if no further activity occurs. If you think your issue is still relevant, commenting will keep it open. Thanks!

@stale stale bot added the stale Did not receive any activity for 60 days label Oct 15, 2023
@krzysztof-cislo
Copy link

"no activity doesn't mean it's not needed" πŸ™‚

@stale stale bot removed the stale Did not receive any activity for 60 days label Nov 10, 2023
Copy link

stale bot commented Feb 11, 2024

It looks like this issue did not receive any activity for 60 days. It will be closed in 7 days if no further activity occurs. If you think your issue is still relevant, commenting will keep it open. Thanks!

@stale stale bot added the stale Did not receive any activity for 60 days label Feb 11, 2024
@krzysztof-cislo
Copy link

Anyone plans to solve this?

@stale stale bot removed the stale Did not receive any activity for 60 days label Feb 11, 2024
@dominikjenik
Copy link

https://stackoverflow.com/questions/78260818/set-expiration-and-automaticaly-refresh-jwt-token-in-nextauth-js/78260819#78260819
Looks like I have found a way how to implement it.

@karar-shah
Copy link

You've got a PR that is almost working from what I've seen, can you not spare a few mates from the team to actually implement it? I'm not hating just asking :)

Can yo please mention the PR, as i am also stuck with the same issue, i want the session token to expire after 1 day instead of 30 days.

Copy link

stale bot commented Jan 21, 2025

It looks like this issue did not receive any activity for 60 days. It will be closed in 7 days if no further activity occurs. If you think your issue is still relevant, commenting will keep it open. Thanks!

@stale stale bot added the stale Did not receive any activity for 60 days label Jan 22, 2025
@CarySmall
Copy link

No Activity does not mean it's not needed πŸ˜†

@stale stale bot removed the stale Did not receive any activity for 60 days label Jan 24, 2025
Copy link

stale bot commented Apr 25, 2025

It looks like this issue did not receive any activity for 60 days. It will be closed in 7 days if no further activity occurs. If you think your issue is still relevant, commenting will keep it open. Thanks!

@stale stale bot added the stale Did not receive any activity for 60 days label Apr 25, 2025
Copy link

stale bot commented May 6, 2025

To keep things tidy, we are closing this issue for now. If you think your issue is still relevant, leave a comment and we might reopen it. Thanks!

@stale stale bot closed this as completed May 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help-needed The maintainer needs help due to time constraint/missing knowledge stale Did not receive any activity for 60 days
Projects
None yet
Development

No branches or pull requests