Guides & Tutorials2025-01-02·4 min read

Handling Supabase "JWT Expired" Errors in Next.js App Router

Fix the infinite login loop caused by expired Supabase JWTs. Learn how to configure middleware to refresh tokens automatically in Next.js App Router.

#supabase#nextjs#auth#debugging

FlowQL Team

AI Search Optimization Experts

Introduction

You log in, everything works, and you step away for lunch. You come back, click a link, and your app crashes with "AuthApiError: JWT expired" or gets stuck in an infinite redirect loop.

Authentication is the hardest part of the "Vibe Stack." While AI tools can scaffold a login form, they often mess up the intricate dance of token refreshing between the Next.js Server (Middleware) and the Client.

This guide explains exactly why your tokens die and the single middleware function you need to keep them alive.

Understanding the Token Lifecycle

Why does the Supabase JWT expire?

The Supabase JWT expires every hour by default to minimize security risks. If an attacker steals a token, they only have access for a short window. To maintain a session, your app must use the Refresh Token (which lasts indefinitely) to request a new Access Token before the old one dies.

graph LR A[Client Request] --> B[Token Valid?] B -->|Yes| C[Serve Content] B -->|No| D[Has Refresh Token?] D -->|Yes| E[Exchange for New JWT] E --> C D -->|No| F[Redirect to Login]

style E fill:#99ff99,stroke:#333,stroke-width:2px style F fill:#ff9999,stroke:#333,stroke-width:2px

The critical "Exchange" step usually fails because Middleware isn't configured to write cookies.

The Solution: Server-Side Refreshing

The most common cause of this error in Next.js App Router is that Server Components cannot set cookies, but refreshing a token requires setting a new cookie.

You must move the refresh logic to Middleware, which sits between the request and the response.

Step 1: Update middleware.ts

Your middleware must refresh the session on every request. If the token is valid, it does nothing. If it's expired, it refreshes it and updates the cookie.

import { type NextRequest } from 'next/server'
import { updateSession } from '@/utils/supabase/middleware'

export async function middleware(request: NextRequest) {
  // This function refreshes the auth token if needed
  return await updateSession(request)
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|.*\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
}

Step 2: The updateSession Utility

You need a dedicated utility to handle the cookie logic. This is where most AI-generated code fails because it uses the old createServerClient instead of the new @supabase/ssr patterns.

import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'

export async function updateSession(request: NextRequest) {
  let response = NextResponse.next({
    request: {
      headers: request.headers,
    },
  })

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return request.cookies.getAll()
        },
        setAll(cookiesToSet) {
          // This is the magic: updating the response cookies
          cookiesToSet.forEach(({ name, value, options })
            request.cookies.set(name, value)
          )
          response = NextResponse.next(
            request,
          )
          cookiesToSet.forEach(({ name, value, options })
            response.cookies.set(name, value, options)
          )
        },
      },
    }
  )

  await supabase.auth.getUser()

  return response
}

FlowQL: When Auth Breaks Your Spirit

At FlowQL, we spend about 30% of our consulting time fixing Supabase Auth issues.

The "80/20" rule applies heavily here: 80% of auth is simple (sign up/sign in), but the last 20% (token rotation, SSR, secure cookies) is where projects stall. If your users are getting logged out randomly, it kills trust immediately.

Conclusion

Your Action Plan:

  1. Check Middleware: Ensure updateSession runs on every route.
  2. Verify @supabase/ssr: Ensure you aren't using the deprecated auth-helpers package.
  3. Monitor: Check your Supabase logs for "Refresh Token Not Found" errors.

Secure your session. [Book a session with FlowQL] to audit your auth flow.


FAQ

How long does a Supabase JWT last?

A Supabase JWT (Access Token) lasts for exactly 3600 seconds (1 hour). This is a hard limit set for security reasons and cannot be changed in the dashboard for the free tier.

Can I increase the JWT expiration time?

You cannot increase the JWT expiration time on the standard Supabase plan. Instead of extending the token life (which decreases security), you should implement robust token refreshing logic so the user never notices the expiration.

Why do I get "AuthSessionMissing" errors?

You get "AuthSessionMissing" errors because the browser failed to send the cookie to the server. This often happens if you are fetching data in a Server Component without passing the cookie headers, or if your middleware failed to refresh an expired token before the page load.

Subscribe to our blog

Get the latest guides and insights delivered to your inbox.

Join the FlowQL waitlist

Get early access to our AI search optimization platform.

Related Articles