From c6d6bb4336fb448ae7fe061bef5ce54f920beed2 Mon Sep 17 00:00:00 2001 From: Katerina Skroumpelou Date: Tue, 9 Dec 2025 14:06:20 +0200 Subject: [PATCH 1/2] fix(auth): add helpful error when PKCE code verifier is missing --- packages/core/auth-js/src/GoTrueClient.ts | 5 ++ packages/core/auth-js/src/lib/errors.ts | 32 ++++++++++++ .../core/auth-js/test/GoTrueClient.test.ts | 52 ++++++++++++++++++- 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/packages/core/auth-js/src/GoTrueClient.ts b/packages/core/auth-js/src/GoTrueClient.ts index 10c738438..20dc034b5 100644 --- a/packages/core/auth-js/src/GoTrueClient.ts +++ b/packages/core/auth-js/src/GoTrueClient.ts @@ -14,6 +14,7 @@ import { AuthInvalidCredentialsError, AuthInvalidJwtError, AuthInvalidTokenResponseError, + AuthPKCECodeVerifierMissingError, AuthPKCEGrantCodeExchangeError, AuthSessionMissingError, AuthUnknownError, @@ -1110,6 +1111,10 @@ export default class GoTrueClient { const [codeVerifier, redirectType] = ((storageItem ?? '') as string).split('/') try { + if (!codeVerifier && this.flowType === 'pkce') { + throw new AuthPKCECodeVerifierMissingError() + } + const { data, error } = await _request( this.fetch, 'POST', diff --git a/packages/core/auth-js/src/lib/errors.ts b/packages/core/auth-js/src/lib/errors.ts index 28270e0a8..6a64f0f45 100644 --- a/packages/core/auth-js/src/lib/errors.ts +++ b/packages/core/auth-js/src/lib/errors.ts @@ -223,6 +223,38 @@ export class AuthPKCEGrantCodeExchangeError extends CustomAuthError { } } +/** + * Error thrown when the PKCE code verifier is not found in storage. + * This typically happens when the auth flow was initiated in a different + * browser, device, or the storage was cleared. + * + * @example + * ```ts + * import { AuthPKCECodeVerifierMissingError } from '@supabase/auth-js' + * + * throw new AuthPKCECodeVerifierMissingError() + * ``` + */ +export class AuthPKCECodeVerifierMissingError extends CustomAuthError { + constructor() { + super( + 'PKCE code verifier not found in storage. ' + + 'This can happen if the auth flow was initiated in a different browser or device, ' + + 'or if the storage was cleared. For server-side auth, ensure you are using ' + + '@supabase/ssr with cookie-based storage.', + 'AuthPKCECodeVerifierMissingError', + 400, + 'pkce_code_verifier_not_found' + ) + } +} + +export function isAuthPKCECodeVerifierMissingError( + error: unknown +): error is AuthPKCECodeVerifierMissingError { + return isAuthError(error) && error.name === 'AuthPKCECodeVerifierMissingError' +} + /** * Error thrown when a transient fetch issue occurs. * diff --git a/packages/core/auth-js/test/GoTrueClient.test.ts b/packages/core/auth-js/test/GoTrueClient.test.ts index 0312b906a..9fdfcecbb 100644 --- a/packages/core/auth-js/test/GoTrueClient.test.ts +++ b/packages/core/auth-js/test/GoTrueClient.test.ts @@ -1,4 +1,4 @@ -import { AuthError } from '../src/lib/errors' +import { AuthError, AuthPKCECodeVerifierMissingError } from '../src/lib/errors' import { STORAGE_KEY } from '../src/lib/constants' import { memoryLocalStorageAdapter } from '../src/lib/local-storage' import GoTrueClient from '../src/GoTrueClient' @@ -455,11 +455,59 @@ describe('GoTrueClient', () => { }) test('exchangeCodeForSession() should fail with invalid authCode', async () => { - const { error } = await pkceClient.exchangeCodeForSession('mock_code') + // Mock fetch to return a 400 error for invalid auth code + const mockFetch = jest.fn().mockResolvedValue({ + ok: false, + status: 400, + headers: new Headers(), + json: () => + Promise.resolve({ + error: 'invalid_grant', + error_description: 'Invalid auth code', + }), + }) + + const storage = memoryLocalStorageAdapter() + const client = new GoTrueClient({ + url: GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON, + autoRefreshToken: false, + persistSession: true, + storage, + flowType: 'pkce', + fetch: mockFetch, + }) + + // Set up a code verifier so we can test the invalid auth code error + // @ts-expect-error 'Allow access to protected storageKey' + const storageKey = client.storageKey + await storage.setItem(`${storageKey}-code-verifier`, 'mock-verifier') + + const { error } = await client.exchangeCodeForSession('mock_code') expect(error).not.toBeNull() expect(error?.status).toEqual(400) }) + + test('exchangeCodeForSession() should throw helpful error when code verifier is missing', async () => { + const storage = memoryLocalStorageAdapter() + // Don't set a code verifier - this simulates the common issue where + // the auth flow was initiated in a different browser/device + + const client = new GoTrueClient({ + url: GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON, + autoRefreshToken: false, + persistSession: true, + storage, + flowType: 'pkce', + }) + + const { error } = await client.exchangeCodeForSession('some-auth-code') + + expect(error).toBeInstanceOf(AuthPKCECodeVerifierMissingError) + expect(error?.message).toContain('PKCE code verifier not found in storage') + expect(error?.message).toContain('@supabase/ssr') + expect(error?.code).toEqual('pkce_code_verifier_not_found') + }) }) describe('Email Auth', () => { From 41ba279a49ea3186a5a296b8fc365227e52385fb Mon Sep 17 00:00:00 2001 From: Katerina Skroumpelou Date: Tue, 9 Dec 2025 14:12:29 +0200 Subject: [PATCH 2/2] fix(storage): update error message --- packages/core/auth-js/src/lib/errors.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/auth-js/src/lib/errors.ts b/packages/core/auth-js/src/lib/errors.ts index 6a64f0f45..7dfa23955 100644 --- a/packages/core/auth-js/src/lib/errors.ts +++ b/packages/core/auth-js/src/lib/errors.ts @@ -240,8 +240,8 @@ export class AuthPKCECodeVerifierMissingError extends CustomAuthError { super( 'PKCE code verifier not found in storage. ' + 'This can happen if the auth flow was initiated in a different browser or device, ' + - 'or if the storage was cleared. For server-side auth, ensure you are using ' + - '@supabase/ssr with cookie-based storage.', + 'or if the storage was cleared. For SSR frameworks (Next.js, SvelteKit, etc.), ' + + 'use @supabase/ssr on both the server and client to store the code verifier in cookies.', 'AuthPKCECodeVerifierMissingError', 400, 'pkce_code_verifier_not_found'