Supabase Chrome Extension: API calls fail after token refresh until manual fetch Problem Description

64 views
Skip to first unread message

Tanzim mahtab

unread,
Aug 30, 2025, 1:20:42 AMAug 30
to Chromium Extensions
For a Chrome extension using Supabase, is using direct fetch instead of invoke() the recommended approach for Chrome extensions?

Hello folks, I'm developing a Chrome extension using Supabase and running into a strange issue with authentication after token refresh events.

Setup:

  • MV3 | React | Webpack | Supabase JS Client 
  • Chrome extension with background script
  • Using onAuthStateChange to listen for auth events
  • Making API calls to Supabase Edge Functions

The Issue:

  1. User authenticates ( login with password ) successfully ✅
  2. API calls work fine initially ✅
  3. Token refresh happens automatically (I can see the TOKEN_REFRESHED event) ✅
  4. I get the new session in the auth state change listener ✅
  5. But then: Any subsequent API calls using supabase.functions.invoke() just hang - no response, no error, nothing
  6. Even supabase.auth.getSession() hangs and returns nothing

Here's the strange part: If I make ANY manual fetch request to Supabase after the token refresh, suddenly all supabase.functions.invoke() calls start working again.

For example, 

a) 

// token refreshed                                                                  // await supabase.functions.invoke('my-function', { ... });                                                            // nothing happened 

b ) 

// token refreshed                                                                                                                              // fetch(`${SUPABASE_URL}/functions/v1/my-function`,)                                                        // it works and after that other supabase.functions.invoke('') works perfectly

. My questions are : 

  1. Has anyone experienced this Chrome extension + Supabase token refresh issue?
  2. Is there a proper way to "refresh" the Supabase client's internal state after token refresh?
  3. Why does a manual fetch request fix the invoke() method?
  4. Is using direct fetch instead of invoke() the recommended approach for Chrome extensions?

Any insights would be greatly appreciated! This seems like it might be related to how Supabase manages internal connection state in the Chrome extension environment.

Thanks in advance.

David Amber “WebDUH LLC” Weatherspoon

unread,
Sep 15, 2025, 4:48:50 AM (11 days ago) Sep 15
to Chromium Extensions, Tanzim mahtab

This is an interesting issue that points to a state synchronization problem between the Supabase client and the Chrome extension's network layer after token refresh. Let me break down what's likely happening and provide solutions.

Root Cause Analysis

The behavior you're describing suggests that the Supabase client's internal fetch implementation isn't properly updating its authorization headers or cookies after a token refresh in the Chrome extension environment. The manual fetch "fixes" it because it likely triggers some internal state update or cookie sync.

Why This Happens in Chrome Extensions

Chrome extensions have unique characteristics that can cause this:

  1. Service Worker Context (MV3): Background scripts in MV3 run as service workers, which have different cookie/storage handling than regular web pages
  2. Cookie Partitioning: Chrome extensions handle cookies differently, especially with cross-origin requests
  3. Fetch Interceptors: The Supabase client may use fetch interceptors that don't properly update in the extension context
Solutions & Workarounds Solution 1: Force Client Refresh After Token Update
javascript
// In your background script supabase.auth.onAuthStateChange(async (event, session) => { if (event === 'TOKEN_REFRESHED' && session) { // Force update the client's auth headers await supabase.auth.setSession({ access_token: session.access_token, refresh_token: session.refresh_token }); // Alternative: Recreate the client instance // reinitializeSupabaseClient(session); } });
Solution 2: Custom Fetch Wrapper (Recommended for Chrome Extensions)
javascript
// Create a custom fetch wrapper that always uses fresh tokens async function supabaseInvoke(functionName, options = {}) { const { data: { session } } = await supabase.auth.getSession(); if (!session) { throw new Error('No active session'); } const response = await fetch(`${SUPABASE_URL}/functions/v1/${functionName}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${session.access_token}`, 'apikey': SUPABASE_ANON_KEY, }, body: JSON.stringify(options.body || {}), }); if (!response.ok) { throw new Error(`Function call failed: ${response.statusText}`); } return response.json(); }
Solution 3: Ensure Proper Cookie Configuration
javascript
// When initializing Supabase client const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { auth: { storage: customExtensionStorage, // Custom storage implementation autoRefreshToken: true, persistSession: true, detectSessionInUrl: false, // Important for extensions flowType: 'pkce', // Use PKCE flow for better security storageKey: 'supabase-auth', // Explicit storage key }, global: { headers: { 'X-Client-Info': 'chrome-extension' }, fetch: (...args) => { // Custom fetch that ensures proper header propagation return fetch(...args); } } });
Solution 4: Custom Storage Adapter for MV3
javascript
// Implement a proper storage adapter for Chrome extensions const customExtensionStorage = { getItem: async (key) => { const result = await chrome.storage.local.get(key); return result[key] || null; }, setItem: async (key, value) => { await chrome.storage.local.set({ [key]: value }); }, removeItem: async (key) => { await chrome.storage.local.remove(key); } };
Answers to Your Specific Questions
  1. Has anyone experienced this? Yes, this is a known issue with Supabase in Chrome extensions, particularly MV3. It's related to how service workers handle fetch and cookies differently.
  2. Proper way to refresh client state? Either manually call setSession() with the new tokens or implement a custom fetch wrapper that always gets fresh tokens.
  3. Why does manual fetch fix it? The manual fetch likely triggers Chrome's internal cookie sync mechanism or causes the Supabase client to re-read its stored session state.
  4. Is direct fetch recommended? Yes, for Chrome extensions, direct fetch is often more reliable. The Supabase client wasn't specifically designed for the Chrome extension environment, so using direct fetch with manual token management gives you more control.
Recommended Approach for Production

For a production Chrome extension, I'd recommend:

javascript
class SupabaseExtensionClient { constructor(url, anonKey) { this.url = url; this.anonKey = anonKey; this.supabase = createClient(url, anonKey, { auth: { storage: customExtensionStorage } }); // Listen for auth changes this.supabase.auth.onAuthStateChange(this.handleAuthChange.bind(this)); } async handleAuthChange(event, session) { if (event === 'TOKEN_REFRESHED') { // Store the fresh session await chrome.storage.local.set({ 'supabase_session': session }); } } async invokeFunction(functionName, body = {}) { // Always get fresh session from storage const { supabase_session } = await chrome.storage.local.get('supabase_session'); if (!supabase_session?.access_token) { throw new Error('No valid session'); } return fetch(`${this.url}/functions/v1/${functionName}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${supabase_session.access_token}`, 'apikey': this.anonKey, }, body: JSON.stringify(body) }).then(res => res.json()); } }

This approach gives you full control over token management and avoids the state synchronization issues you're experiencing. It's more verbose but much more reliable in the Chrome extension context.

Reply all
Reply to author
Forward
0 new messages