next.jsjwtnext-authapp-routercredential-providers

How to properly handle token expiry with Auth.js (NextAuth v5)?


I’m working on a Next.js project using NextAuth (v5) for authentication. I want to implement a 10-second token expiry for testing purposes. The goal is that after logging in, users can access protected routes for 10 seconds. Once the token expires, refreshing the page or navigating should redirect them to the login page.

Here’s my current implementation:

auth.ts

import NextAuth from "next-auth"
import authConfig from "@/auth.config"
 
export const { handlers, signIn, signOut, auth } = NextAuth({
  ...authConfig,
  pages: {
    signIn: '/sign-in'
  },
  session: {
    strategy: "jwt",
    // maxAge: <number>,
    // updateAge: <number>,
  },
  callbacks: {
    async jwt({ token, user }) {
      console.log('\n hello from jwt callback. \n'); // DEBUG
      if (user){
        token.firstName = user.firstName;
        token.lastName = user.lastName;
        token.email = user.email;
      }
      return token;
    },

    async session({ session, token }) {
      console.log('\n hello from session callback. \n'); // DEBUG
      session.user.firstName = token.firstName as string;
      session.user.lastName = token.lastName as string;
      session.user.email = token.email as string; 
      return session;
    },
  },
});

middleware.ts

import authConfig from "@/auth.config"
import NextAuth from "next-auth"
import { NextResponse } from "next/server";

const { auth: middleware } = NextAuth(authConfig);

export default  middleware( async (req) => {
  const url = req.nextUrl.clone();
  if(url.pathname.startsWith('/api/auth')) return NextResponse.next();
  if(url.pathname.startsWith('/api/users')) return NextResponse.next();

  const isLoggedIn = !!req.auth;

  if(!isLoggedIn){
    if(url.pathname.startsWith('/api')){ 
      return NextResponse.json(
        { message: "Unauthorized" },
        { status: 401 } 
      );
    }else{
      url.pathname = '/sign-in'
      return NextResponse.redirect(url);
    }
  }
  return NextResponse.next();
})
 
export const config = {
  matcher: [
    "/((?!.*\\.[\\w]+$|_next|sign-in|sign-up).*)", // Match all paths except static files, `_next`, `sign-in` and `sign-up` paths
    "/",                           // Root route
    "/api/:path*",                 // API routes
    "/trpc/:path*",                // TRPC routes
  ],
};

My Attempts:

Expected Behavior:

Note:

Since this implementation is based on NextAuth v5 (the latest version at the time), it may feel unfamiliar or different to developers who are used to older stable versions of NextAuth (i.e., v4). Some concepts and APIs, such as token handling and session management, may have changed.


Solution

  • You can update like this.

    1. Set Token Expiration In your auth.ts file, configure the session option with the maxAge property to define the token's lifespan:

    session: {
      strategy: "jwt",
      maxAge: 10, // Token will expire after 10 seconds
    },

    1. Handle Token Expiry in Callbacks To ensure the token expires and is invalidated properly, add logic to the jwt callback:

    callbacks: {
      async jwt({ token, user }) {
        const now = Math.floor(Date.now() / 1000); 
        const maxAge = 10; 
    
    
        if (user) {
          token.issuedAt = now;
        }
    
        if (now - (token.issuedAt ?? 0) > maxAge) {
          return null; 
        }
    
        return token;
      },
    
      async session({ session, token }) {
        if (!token) {
          return null;
        }
    
        session.user = {
          firstName: token.firstName,
          lastName: token.lastName,
          email: token.email,
        };
    
        return session;
      },
    },

    1. Validate Tokens in Middleware Update middleware.ts to check token validity and redirect users when the token has expired:

    export default async function middleware(req) {
      const { auth } = req; 
      const url = req.nextUrl.clone();
    
      if (!auth) {
        if (url.pathname.startsWith('/api')) {
          return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
        }
        url.pathname = '/sign-in';
        return NextResponse.redirect(url);
      }
    
      const now = Math.floor(Date.now() / 1000);
      if (auth.token?.issuedAt && now - auth.token.issuedAt > 10) {
        url.pathname = '/sign-in';
        return NextResponse.redirect(url);
      }
    
      return NextResponse.next();
    }