Skip to main content
RapidDev - Software Development Agency
supabase-tutorial

How to Refresh a Session Token in Supabase

The Supabase JS client automatically refreshes session tokens before they expire using the refresh token stored in localStorage or cookies. You do not need to manually refresh in most cases. If you need to force a refresh, call supabase.auth.refreshSession(). Listen for the TOKEN_REFRESHED event via onAuthStateChange to react when a new token is issued. The default JWT expiry is 3600 seconds (1 hour), configurable in the Dashboard.

What you'll learn

  • How automatic token refresh works in the Supabase JS client
  • How to manually trigger a token refresh with refreshSession()
  • How to listen for TOKEN_REFRESHED events with onAuthStateChange
  • How to configure JWT expiry time in the Supabase Dashboard
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read10-15 minSupabase (all plans), @supabase/supabase-js v2+March 2026RapidDev Engineering Team
TL;DR

The Supabase JS client automatically refreshes session tokens before they expire using the refresh token stored in localStorage or cookies. You do not need to manually refresh in most cases. If you need to force a refresh, call supabase.auth.refreshSession(). Listen for the TOKEN_REFRESHED event via onAuthStateChange to react when a new token is issued. The default JWT expiry is 3600 seconds (1 hour), configurable in the Dashboard.

Understanding and Managing Session Token Refresh in Supabase

Supabase Auth uses JWTs (JSON Web Tokens) for session management. Each JWT has an expiry time, and the client automatically refreshes the token before it expires using a long-lived refresh token. This tutorial explains how the auto-refresh mechanism works, when you might need to manually refresh, and how to handle edge cases like background tabs, network interruptions, and long-running sessions.

Prerequisites

  • A Supabase project with authentication enabled
  • The Supabase JS client installed (@supabase/supabase-js v2)
  • A basic understanding of how JWTs and refresh tokens work

Step-by-step guide

1

Understand how automatic token refresh works

When a user logs in, Supabase returns two tokens: an access token (JWT) and a refresh token. The access token has a short lifespan (default 3600 seconds / 1 hour) and is sent with every API request. The refresh token has a much longer lifespan and is used to obtain new access tokens. The Supabase JS client automatically refreshes the access token when approximately 80% of its lifetime has elapsed. This happens silently in the background — no user interaction or code is needed.

typescript
1// The client handles refresh automatically — this just works
2import { createClient } from '@supabase/supabase-js'
3
4const supabase = createClient(
5 process.env.NEXT_PUBLIC_SUPABASE_URL!,
6 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
7)
8
9// autoRefreshToken is true by default
10// The client will refresh the token ~12 minutes before expiry (for 1-hour tokens)

Expected result: The client silently refreshes the access token before it expires, and API requests continue to work without interruption.

2

Listen for TOKEN_REFRESHED events

The onAuthStateChange listener fires a TOKEN_REFRESHED event every time the client successfully refreshes the access token. This is useful for updating your app state with the new session data, logging token refreshes for debugging, or syncing the session across components. Register the listener in your root component and clean it up on unmount to prevent memory leaks.

typescript
1import { supabase } from '@/lib/supabase'
2
3const { data: { subscription } } = supabase.auth.onAuthStateChange(
4 (event, session) => {
5 switch (event) {
6 case 'TOKEN_REFRESHED':
7 console.log('Token was refreshed at:', new Date().toISOString())
8 console.log('New expiry:', new Date(session!.expires_at! * 1000).toISOString())
9 break
10 case 'SIGNED_OUT':
11 console.log('User signed out')
12 break
13 case 'SIGNED_IN':
14 console.log('User signed in')
15 break
16 }
17 }
18)
19
20// Clean up when done
21// subscription.unsubscribe()

Expected result: Your callback fires every time the token is refreshed, giving you the updated session with the new access token and expiry time.

3

Manually refresh the session when needed

In most cases, automatic refresh is sufficient. However, there are edge cases where you may need to force a refresh: after a user updates their profile metadata (which is embedded in the JWT), when recovering from a network outage, or when your app detects an expired token. Call supabase.auth.refreshSession() to immediately obtain a new access token using the stored refresh token.

typescript
1// Force a manual refresh
2const { data: { session }, error } = await supabase.auth.refreshSession()
3
4if (error) {
5 console.error('Refresh failed:', error.message)
6 // If refresh fails, the refresh token is likely expired
7 // Redirect the user to login
8 window.location.href = '/login'
9} else {
10 console.log('New session:', session)
11 console.log('Token expires at:', new Date(session!.expires_at! * 1000))
12}
13
14// You can also pass an existing refresh token explicitly
15const { data, error: err } = await supabase.auth.refreshSession({
16 refresh_token: 'your-refresh-token-here'
17})

Expected result: A new access token is issued immediately, and the session is updated in localStorage or cookies.

4

Configure JWT expiry time in the Dashboard

The default JWT expiry is 3600 seconds (1 hour). You can change this in the Supabase Dashboard. Go to Authentication > Settings > JWT Expiry and enter a new value in seconds. Shorter expiry times are more secure (less window if a token is stolen) but increase the frequency of refresh requests. Longer expiry times reduce API calls but increase the security risk window. Most production apps keep the default of 3600 seconds.

Expected result: New tokens issued after the change will use the updated expiry time. Existing sessions will continue with their original expiry until they are refreshed.

5

Handle token refresh failures gracefully

Token refresh can fail for several reasons: the refresh token has expired (default 1 week), the user has been deleted, the network is down, or the Supabase project is paused. Your app should handle these failures gracefully by detecting the error and redirecting the user to login. Listen for the SIGNED_OUT event which fires when the client cannot refresh the session and gives up.

typescript
1import { supabase } from '@/lib/supabase'
2
3supabase.auth.onAuthStateChange((event, session) => {
4 if (event === 'SIGNED_OUT') {
5 // Session expired and could not be refreshed
6 // Clear any local state
7 localStorage.removeItem('user-preferences')
8 // Redirect to login
9 window.location.href = '/login'
10 }
11})
12
13// Wrap API calls to handle expired tokens
14async function fetchWithAuth(tableName: string) {
15 const { data, error } = await supabase
16 .from(tableName)
17 .select('*')
18
19 if (error?.message?.includes('JWT expired')) {
20 // Try to refresh
21 const { error: refreshError } = await supabase.auth.refreshSession()
22 if (refreshError) {
23 window.location.href = '/login'
24 return null
25 }
26 // Retry the original request
27 const { data: retryData } = await supabase.from(tableName).select('*')
28 return retryData
29 }
30
31 return data
32}

Expected result: Your app detects refresh failures and gracefully redirects to the login page instead of showing broken API error states.

Complete working example

session-refresh-manager.ts
1// Complete session refresh management for Supabase
2import { createClient, Session } from '@supabase/supabase-js'
3
4const supabase = createClient(
5 process.env.NEXT_PUBLIC_SUPABASE_URL!,
6 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
7)
8
9// Track the current session
10let currentSession: Session | null = null
11
12// Register the auth state listener
13const { data: { subscription } } = supabase.auth.onAuthStateChange(
14 (event, session) => {
15 currentSession = session
16
17 switch (event) {
18 case 'INITIAL_SESSION':
19 console.log('Session loaded from storage')
20 break
21 case 'SIGNED_IN':
22 console.log('User signed in:', session?.user?.email)
23 break
24 case 'TOKEN_REFRESHED':
25 console.log('Token refreshed, new expiry:',
26 session?.expires_at
27 ? new Date(session.expires_at * 1000).toISOString()
28 : 'unknown'
29 )
30 break
31 case 'SIGNED_OUT':
32 console.log('User signed out or session expired')
33 handleSignOut()
34 break
35 }
36 }
37)
38
39// Force refresh (call after profile updates)
40async function forceRefresh(): Promise<Session | null> {
41 const { data: { session }, error } = await supabase.auth.refreshSession()
42 if (error) {
43 console.error('Refresh failed:', error.message)
44 return null
45 }
46 return session
47}
48
49// Check if the current token is close to expiring
50function isTokenExpiringSoon(): boolean {
51 if (!currentSession?.expires_at) return true
52 const expiresAt = currentSession.expires_at * 1000
53 const now = Date.now()
54 const fiveMinutes = 5 * 60 * 1000
55 return (expiresAt - now) < fiveMinutes
56}
57
58// Handle sign out / expired session
59function handleSignOut() {
60 currentSession = null
61 if (typeof window !== 'undefined') {
62 window.location.href = '/login'
63 }
64}
65
66// Cleanup
67function cleanup() {
68 subscription.unsubscribe()
69}
70
71export { supabase, forceRefresh, isTokenExpiringSoon, cleanup }

Common mistakes when refreshing a Session Token in Supabase

Why it's a problem: Manually implementing a setInterval to refresh tokens instead of relying on the built-in auto-refresh

How to avoid: The Supabase client handles token refresh automatically. Remove any custom refresh intervals. They can cause race conditions and duplicate refresh requests.

Why it's a problem: Setting autoRefreshToken to false without implementing a custom refresh mechanism

How to avoid: Leave autoRefreshToken at its default value of true. Only set it to false if you have a specific architectural reason and are handling refresh yourself.

Why it's a problem: Not handling the case where refreshSession() fails because the refresh token has expired

How to avoid: Always check for errors from refreshSession(). If it fails, redirect the user to the login page. The refresh token has a separate (longer) expiry from the access token.

Best practices

  • Leave autoRefreshToken enabled (the default) and let the Supabase client handle token refresh automatically
  • Register onAuthStateChange in your root component to react to TOKEN_REFRESHED and SIGNED_OUT events
  • Call refreshSession() manually only when needed, such as after updating user metadata or recovering from errors
  • Keep the default JWT expiry of 3600 seconds unless you have a specific security requirement to change it
  • Handle the SIGNED_OUT event to gracefully redirect users to the login page when sessions expire
  • Never store the refresh token in a custom location — let the Supabase client manage token storage

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

Explain how Supabase session token refresh works, including auto-refresh, manual refresh with refreshSession(), and the TOKEN_REFRESHED event. Show TypeScript code for handling all token lifecycle events in a React app.

Supabase Prompt

Show me how to manage Supabase session token refresh in a React app. Include onAuthStateChange setup for TOKEN_REFRESHED events, manual refresh after profile updates, and graceful handling when the refresh token expires.

Frequently asked questions

Does the Supabase client refresh tokens automatically?

Yes. The client refreshes the access token when approximately 80% of its lifetime has elapsed. With the default 1-hour expiry, the client refreshes around the 48-minute mark. No custom code is needed.

When should I call refreshSession() manually?

Call it after updating user metadata with updateUser(), when recovering from a network outage, or when your app detects a JWT expired error. In normal operation, the auto-refresh handles everything.

What happens if the refresh token itself expires?

If the refresh token expires, the session cannot be renewed and the user must log in again. The SIGNED_OUT event fires automatically. The default refresh token lifetime is configurable in the Dashboard.

Does token refresh work in background tabs?

Browsers throttle timers in background tabs, which can delay the refresh. The Supabase client uses a visibility change listener to attempt a refresh when the tab becomes active again. Very long periods in background (hours) may cause the token to expire.

Can I change the JWT expiry time?

Yes. Go to Dashboard > Authentication > Settings and update the JWT Expiry field. The value is in seconds. Changes only affect new tokens — existing sessions keep their original expiry until they are refreshed.

How do I know when a token was last refreshed?

Listen for the TOKEN_REFRESHED event via onAuthStateChange. The session object in the callback contains expires_at, which tells you when the new token will expire. Log this value during development for debugging.

Can RapidDev help me configure session management for my Supabase app?

Yes. RapidDev can help you configure JWT settings, implement custom refresh logic, set up SSR-compatible sessions, and handle complex auth flows across multiple platforms.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.