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

How to Log Out a User in Supabase

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.

What you'll learn

  • How to implement signOut() with proper session cleanup
  • How to listen for the SIGNED_OUT event and update app state
  • How to implement global sign-out across all devices
  • How to handle sign-out in React with proper navigation
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read5-10 minSupabase (all plans), @supabase/supabase-js v2+March 2026RapidDev Engineering Team
TL;DR

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

1

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.

typescript
1import { createClient } from '@supabase/supabase-js';
2
3const supabase = createClient(
4 process.env.NEXT_PUBLIC_SUPABASE_URL!,
5 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
6);
7
8// Basic sign-out (current session only)
9async function handleSignOut() {
10 const { error } = await supabase.auth.signOut();
11
12 if (error) {
13 console.error('Sign out error:', error.message);
14 // Local session is still cleared even if server call fails
15 }
16
17 // Redirect to login page
18 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.

2

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.

typescript
1// Global sign-out — logs out all devices
2async function handleGlobalSignOut() {
3 const { error } = await supabase.auth.signOut({ scope: 'global' });
4
5 if (error) {
6 console.error('Global sign out error:', error.message);
7 }
8
9 window.location.href = '/login';
10}
11
12// Local sign-out — only current device (default)
13async function handleLocalSignOut() {
14 const { error } = await supabase.auth.signOut({ scope: 'local' });
15
16 if (error) {
17 console.error('Sign out error:', error.message);
18 }
19
20 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.

3

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.

typescript
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 state
6 localStorage.removeItem('app-cache');
7 sessionStorage.clear();
8
9 // Redirect to login if not already there
10 if (window.location.pathname !== '/login') {
11 window.location.href = '/login';
12 }
13 }
14
15 if (event === 'SIGNED_IN') {
16 // User just logged in — initialize app state
17 console.log('User signed in:', session?.user?.email);
18 }
19 }
20);
21
22// Clean up the listener when the app unmounts
23// 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.

4

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.

typescript
1import { useState } from 'react';
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
9export function SignOutButton() {
10 const [loading, setLoading] = useState(false);
11
12 async function handleSignOut() {
13 setLoading(true);
14
15 const { error } = await supabase.auth.signOut();
16
17 if (error) {
18 console.error('Sign out failed:', error.message);
19 setLoading(false);
20 return;
21 }
22
23 // Full page redirect to clear all state
24 window.location.href = '/login';
25 }
26
27 return (
28 <button
29 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.

5

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.

typescript
1// Next.js App Router: Server Action for sign-out
2// app/actions/auth.ts
3'use server';
4
5import { createClient } from '@/lib/supabase/server';
6import { redirect } from 'next/navigation';
7
8export async function signOut() {
9 const supabase = await createClient();
10 await supabase.auth.signOut();
11 redirect('/login');
12}
13
14// Usage in a Server Component or Client Component
15// app/components/sign-out-button.tsx
16import { signOut } from '@/app/actions/auth';
17
18export 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

auth-sign-out.ts
1// Complete sign-out implementation for Supabase
2// Handles local, global, and SSR sign-out scenarios
3
4import { createClient } from '@supabase/supabase-js';
5
6const supabase = createClient(
7 process.env.NEXT_PUBLIC_SUPABASE_URL!,
8 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
9);
10
11type SignOutScope = 'local' | 'global';
12
13// Sign out with configurable scope
14export async function signOut(scope: SignOutScope = 'local'): Promise<void> {
15 const { error } = await supabase.auth.signOut({ scope });
16
17 if (error) {
18 console.error(`Sign out error (${scope}):`, error.message);
19 // Local session is cleared regardless of server error
20 }
21
22 // Clear any application-specific cached data
23 clearAppState();
24
25 // Full page redirect to ensure clean state
26 window.location.href = '/login';
27}
28
29// Clear application state on sign-out
30function clearAppState(): void {
31 // Remove app-specific localStorage keys
32 const appKeys = ['app-cache', 'user-preferences', 'draft-data'];
33 appKeys.forEach((key) => localStorage.removeItem(key));
34
35 // Clear sessionStorage entirely
36 sessionStorage.clear();
37}
38
39// 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 );
59
60 // Return cleanup function
61 return () => subscription.unsubscribe();
62}
63
64// Check if user is currently signed in
65export 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.

ChatGPT Prompt

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.

Supabase Prompt

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.

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.