To log out a user in Supabase, call supabase.auth.signOut() which clears the local session, invalidates the refresh token, and fires the SIGNED_OUT event on the onAuthStateChange listener. After sign-out, redirect the user to the login page and clean up any application state. For global sign-out across all devices, pass the scope: 'global' option to invalidate all of the user's sessions simultaneously.
Implementing User Logout in Supabase
Logging out a user in Supabase involves more than just calling signOut(). You need to handle the SIGNED_OUT auth event, clear any cached application state, redirect to an unauthenticated page, and decide whether to sign out the current session or all sessions globally. This tutorial covers the complete sign-out flow with proper cleanup and edge case handling.
Prerequisites
- A Supabase project with authentication configured
- The Supabase JS client installed and initialized
- Users who can sign in (email/password, OAuth, or magic link)
- Basic understanding of React or your frontend framework
Step-by-step guide
Implement basic sign-out with supabase.auth.signOut()
Implement basic sign-out with supabase.auth.signOut()
The signOut() method clears the user's session from local storage, invalidates the refresh token on the server, and triggers the SIGNED_OUT event. After calling signOut(), the user's JWT is no longer valid and subsequent API requests will use the anonymous (anon) role. Check for errors — sign-out can fail if the network is unavailable, though the local session is still cleared.
1import { createClient } from '@supabase/supabase-js';23const supabase = createClient(4 process.env.NEXT_PUBLIC_SUPABASE_URL!,5 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!6);78// Basic sign-out (current session only)9async function handleSignOut() {10 const { error } = await supabase.auth.signOut();1112 if (error) {13 console.error('Sign out error:', error.message);14 // Local session is still cleared even if server call fails15 }1617 // Redirect to login page18 window.location.href = '/login';19}Expected result: The user's session is cleared locally and on the server. The SIGNED_OUT event fires on any active onAuthStateChange listeners.
Implement global sign-out across all devices
Implement global sign-out across all devices
By default, signOut() only invalidates the current session. If the user is logged in on multiple devices or browsers, those sessions remain active. Pass scope: 'global' to invalidate all sessions for the user. This is useful for security-sensitive actions like password changes or when the user explicitly wants to log out everywhere.
1// Global sign-out — logs out all devices2async function handleGlobalSignOut() {3 const { error } = await supabase.auth.signOut({ scope: 'global' });45 if (error) {6 console.error('Global sign out error:', error.message);7 }89 window.location.href = '/login';10}1112// Local sign-out — only current device (default)13async function handleLocalSignOut() {14 const { error } = await supabase.auth.signOut({ scope: 'local' });1516 if (error) {17 console.error('Sign out error:', error.message);18 }1920 window.location.href = '/login';21}Expected result: All of the user's active sessions are invalidated. Other devices will lose access on their next API request or token refresh.
Listen for the SIGNED_OUT event with onAuthStateChange
Listen for the SIGNED_OUT event with onAuthStateChange
Set up an onAuthStateChange listener to react to sign-out events globally in your application. This fires regardless of how the sign-out was triggered — whether by your logout button, token expiration, or global sign-out from another device. Use this listener to clean up application state, reset stores, and redirect to the login page.
1// Set up global auth state listener (typically in your app root)2const { data: { subscription } } = supabase.auth.onAuthStateChange(3 (event, session) => {4 if (event === 'SIGNED_OUT') {5 // Clear any cached application state6 localStorage.removeItem('app-cache');7 sessionStorage.clear();89 // Redirect to login if not already there10 if (window.location.pathname !== '/login') {11 window.location.href = '/login';12 }13 }1415 if (event === 'SIGNED_IN') {16 // User just logged in — initialize app state17 console.log('User signed in:', session?.user?.email);18 }19 }20);2122// Clean up the listener when the app unmounts23// subscription.unsubscribe();Expected result: Your application reacts to sign-out events globally, clearing state and redirecting to login regardless of how the sign-out was triggered.
Build a sign-out button in React
Build a sign-out button in React
Create a reusable logout button component that handles the sign-out flow, shows loading state during the async operation, and handles errors gracefully. The component should disable the button while sign-out is in progress to prevent double clicks.
1import { useState } from 'react';2import { createClient } from '@supabase/supabase-js';34const supabase = createClient(5 process.env.NEXT_PUBLIC_SUPABASE_URL!,6 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!7);89export function SignOutButton() {10 const [loading, setLoading] = useState(false);1112 async function handleSignOut() {13 setLoading(true);1415 const { error } = await supabase.auth.signOut();1617 if (error) {18 console.error('Sign out failed:', error.message);19 setLoading(false);20 return;21 }2223 // Full page redirect to clear all state24 window.location.href = '/login';25 }2627 return (28 <button29 onClick={handleSignOut}30 disabled={loading}31 >32 {loading ? 'Signing out...' : 'Sign out'}33 </button>34 );35}Expected result: A functional sign-out button that handles loading state, errors, and redirects after successful logout.
Handle sign-out in server-side rendered apps
Handle sign-out in server-side rendered apps
For Next.js or other SSR frameworks using @supabase/ssr, sign-out requires clearing the session cookies on the server. Create a server action or API route that calls signOut() using the server-side Supabase client. This ensures the session cookies are properly deleted from both the client and server.
1// Next.js App Router: Server Action for sign-out2// app/actions/auth.ts3'use server';45import { createClient } from '@/lib/supabase/server';6import { redirect } from 'next/navigation';78export async function signOut() {9 const supabase = await createClient();10 await supabase.auth.signOut();11 redirect('/login');12}1314// Usage in a Server Component or Client Component15// app/components/sign-out-button.tsx16import { signOut } from '@/app/actions/auth';1718export function SignOutButton() {19 return (20 <form action={signOut}>21 <button type="submit">Sign out</button>22 </form>23 );24}Expected result: The session is cleared on both the client and server. The user is redirected to the login page without stale session cookies.
Complete working example
1// Complete sign-out implementation for Supabase2// Handles local, global, and SSR sign-out scenarios34import { createClient } from '@supabase/supabase-js';56const supabase = createClient(7 process.env.NEXT_PUBLIC_SUPABASE_URL!,8 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!9);1011type SignOutScope = 'local' | 'global';1213// Sign out with configurable scope14export async function signOut(scope: SignOutScope = 'local'): Promise<void> {15 const { error } = await supabase.auth.signOut({ scope });1617 if (error) {18 console.error(`Sign out error (${scope}):`, error.message);19 // Local session is cleared regardless of server error20 }2122 // Clear any application-specific cached data23 clearAppState();2425 // Full page redirect to ensure clean state26 window.location.href = '/login';27}2829// Clear application state on sign-out30function clearAppState(): void {31 // Remove app-specific localStorage keys32 const appKeys = ['app-cache', 'user-preferences', 'draft-data'];33 appKeys.forEach((key) => localStorage.removeItem(key));3435 // Clear sessionStorage entirely36 sessionStorage.clear();37}3839// Initialize auth state monitoring (call once in app root)40export function initAuthStateMonitor(): () => void {41 const { data: { subscription } } = supabase.auth.onAuthStateChange(42 (event, session) => {43 switch (event) {44 case 'SIGNED_OUT':45 clearAppState();46 if (window.location.pathname !== '/login') {47 window.location.href = '/login';48 }49 break;50 case 'SIGNED_IN':51 console.log('User signed in:', session?.user?.email);52 break;53 case 'TOKEN_REFRESHED':54 console.log('Session token refreshed');55 break;56 }57 }58 );5960 // Return cleanup function61 return () => subscription.unsubscribe();62}6364// Check if user is currently signed in65export async function isSignedIn(): Promise<boolean> {66 const { data: { user } } = await supabase.auth.getUser();67 return !!user;68}Common mistakes when logging Out a User in Supabase
Why it's a problem: Using client-side router navigation after sign-out instead of a full page redirect, leaving stale state in memory
How to avoid: Use window.location.href = '/login' for a full page reload that clears all JavaScript state, or explicitly reset all stores and caches.
Why it's a problem: Not handling the SIGNED_OUT event in onAuthStateChange, causing stale UI after token expiration or global sign-out
How to avoid: Always set up an onAuthStateChange listener in your app root that handles SIGNED_OUT by clearing state and redirecting.
Why it's a problem: In SSR apps, only signing out on the client without clearing server-side session cookies
How to avoid: Use a server action or API route that calls signOut() on the server-side Supabase client to properly clear cookies.
Best practices
- Use window.location.href for post-sign-out redirects to ensure all in-memory state is cleared
- Set up an onAuthStateChange listener in the app root to handle sign-out events globally
- Clear application-specific localStorage and sessionStorage data on sign-out
- Use scope: 'global' for sign-out after password changes or security-sensitive actions
- Handle sign-out errors gracefully — the local session is cleared even if the server call fails
- In SSR frameworks, sign out through a server action to clear session cookies on both client and server
- Disable the sign-out button while the async operation is in progress to prevent double clicks
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I need to implement a complete sign-out flow in my Supabase app with React. Show me how to call signOut, handle the SIGNED_OUT event, clear cached state, and redirect to the login page. Include both local and global sign-out options.
Create a sign-out implementation for a Supabase + Next.js App Router application that handles both client-side and server-side session cleanup, listens for auth state changes, and properly redirects after logout.
Frequently asked questions
Does signOut() clear the local session even if the server call fails?
Yes. The local session is always cleared regardless of whether the server-side token invalidation succeeds. If the network is unavailable, the refresh token remains valid on the server until it expires naturally.
What is the difference between local and global sign-out?
Local sign-out (default) only invalidates the current session on the current device. Global sign-out invalidates all of the user's sessions across all devices and browsers simultaneously.
How do I sign out a user from the server side?
Use the Supabase Admin API with the service_role key: supabase.auth.admin.signOut(userId). This invalidates all sessions for that user and is useful for admin actions like account suspension.
Why is the user still seeing authenticated content after signing out?
This usually means stale data is cached in your state management (Redux, Zustand) or localStorage. Clear all app caches in the onAuthStateChange SIGNED_OUT handler, or use a full page redirect with window.location.href.
Can I sign out a user when their JWT expires?
JWT expiration triggers an automatic token refresh using the refresh token. If the refresh fails, the TOKEN_REFRESHED event fires with a null session. Handle this in onAuthStateChange to force a sign-out and redirect to login.
Does sign-out cancel active Realtime subscriptions?
Signing out does not automatically remove Realtime channel subscriptions. Call supabase.removeAllChannels() before or after signOut() to clean up active subscriptions and prevent connection leaks.
Can RapidDev help implement secure authentication flows in my Supabase app?
Yes. RapidDev can build complete auth flows including sign-in, sign-out, session management, and security features like global sign-out, token refresh handling, and brute force protection.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation