NextAuth.js — Ensuring Linked Accounts Always Have the Latest Access Token

NextAuth.js — Ensuring Linked Accounts Always Have the Latest Access Token

By default, when a user logs out and signs in again in NextAuth.js, the linked account may retain an old access token. This can cause issues if the access token has expired. To always have the latest token, you need to update the linked account during the signIn callback.


1️⃣ The default behavior

NextAuth.js stores linked accounts in the database. On subsequent logins:

  • The account is matched
  • Access token is reused if not expired
  • No automatic refresh occurs unless you explicitly handle it

> Problem: If the access token is expired, API calls using that token will fail.


2️⃣ Update linked account on sign-in

You can use the signIn callback in NextAuth.js to refresh tokens and update the database:



import { PrismaAdapter } from "@next-auth/prisma-adapter";

import NextAuth from "next-auth";

import KeycloakProvider from "next-auth/providers/keycloak";

import prisma from "../../../lib/prisma";

export default NextAuth({

  adapter: PrismaAdapter(prisma),

  providers: [

    KeycloakProvider({

      clientId: process.env.KEYCLOAK_CLIENT_ID,

      clientSecret: process.env.KEYCLOAK_CLIENT_SECRET,

      issuer: process.env.KEYCLOAK_ISSUER

    })

  ],

  callbacks: {

    async signIn({ user, account }) {

      if (account) {

        // Update linked account with latest tokens

        await prisma.account.updateMany({

          where: { provider: account.provider, providerAccountId: account.providerAccountId },

          data: {

            access_token: account.access_token,

            refresh_token: account.refresh_token,

            expires_at: account.expires_at

          }

        });

      }

      return true;

    },

    async jwt({ token, account }) {

      if (account) {

        token.accessToken = account.access_token;

        token.refreshToken = account.refresh_token;

        token.expires = account.expires_at * 1000;

      }

      return token;

    },

    async session({ session, token }) {

      session.accessToken = token.accessToken;

      session.refreshToken = token.refreshToken;

      return session;

    }

  }

});

> Key points:

  • Use the signIn callback to update the linked account
  • Store the latest access_token, refresh_token, and expires_at
  • JWT and session callbacks propagate the latest token to the session

3️⃣ Benefits

  • Every login will use the latest access token
  • API calls from the frontend or backend using the token won’t fail due to expiration
  • Keeps linked accounts synchronized with the IdP

4️⃣ Optional: Refresh tokens automatically

You can also implement **automatic token refresh** using the JWT callback when the token is near expiry:



if (Date.now() > token.expires) {

  const refreshed = await refreshAccessToken(token);

  token.accessToken = refreshed.accessToken;

  token.refreshToken = refreshed.refreshToken;

  token.expires = refreshed.expires;

}

> Combined with updating the linked account on sign-in, this ensures your Next.js app always has valid tokens.


Conclusion

NextAuth.js makes it easy to manage linked accounts, but you must handle token updates yourself if you want **fresh tokens on every login**. Using the signIn callback to update the database ensures your access tokens and refresh tokens are always current, avoiding unexpected 401 errors.

Part of the Next.js Frontend SSO Series

❤️ Support This Blog


If this post helped you, you can support my writing with a small donation. Thank you for reading.


Comments

Popular posts from this blog

fixed: embedded-redis: Unable to run on macOS Sonoma

Copying MDC Context Map in Web Clients: A Comprehensive Guide

Reset user password for your own Ghost blog