Skip to content

Commit 50d9c26

Browse files
committed
fix(auth): add helpful error when PKCE code verifier is missing
1 parent 66351aa commit 50d9c26

File tree

3 files changed

+87
-2
lines changed

3 files changed

+87
-2
lines changed

packages/core/auth-js/src/GoTrueClient.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
AuthInvalidCredentialsError,
1515
AuthInvalidJwtError,
1616
AuthInvalidTokenResponseError,
17+
AuthPKCECodeVerifierMissingError,
1718
AuthPKCEGrantCodeExchangeError,
1819
AuthSessionMissingError,
1920
AuthUnknownError,
@@ -1110,6 +1111,10 @@ export default class GoTrueClient {
11101111
const [codeVerifier, redirectType] = ((storageItem ?? '') as string).split('/')
11111112

11121113
try {
1114+
if (!codeVerifier && this.flowType === 'pkce') {
1115+
throw new AuthPKCECodeVerifierMissingError()
1116+
}
1117+
11131118
const { data, error } = await _request(
11141119
this.fetch,
11151120
'POST',

packages/core/auth-js/src/lib/errors.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,38 @@ export class AuthPKCEGrantCodeExchangeError extends CustomAuthError {
223223
}
224224
}
225225

226+
/**
227+
* Error thrown when the PKCE code verifier is not found in storage.
228+
* This typically happens when the auth flow was initiated in a different
229+
* browser, device, or the storage was cleared.
230+
*
231+
* @example
232+
* ```ts
233+
* import { AuthPKCECodeVerifierMissingError } from '@supabase/auth-js'
234+
*
235+
* throw new AuthPKCECodeVerifierMissingError()
236+
* ```
237+
*/
238+
export class AuthPKCECodeVerifierMissingError extends CustomAuthError {
239+
constructor() {
240+
super(
241+
'PKCE code verifier not found in storage. ' +
242+
'This can happen if the auth flow was initiated in a different browser or device, ' +
243+
'or if the storage was cleared. For server-side auth, ensure you are using ' +
244+
'@supabase/ssr with cookie-based storage.',
245+
'AuthPKCECodeVerifierMissingError',
246+
400,
247+
'pkce_code_verifier_not_found'
248+
)
249+
}
250+
}
251+
252+
export function isAuthPKCECodeVerifierMissingError(
253+
error: unknown
254+
): error is AuthPKCECodeVerifierMissingError {
255+
return isAuthError(error) && error.name === 'AuthPKCECodeVerifierMissingError'
256+
}
257+
226258
/**
227259
* Error thrown when a transient fetch issue occurs.
228260
*

packages/core/auth-js/test/GoTrueClient.test.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AuthError } from '../src/lib/errors'
1+
import { AuthError, AuthPKCECodeVerifierMissingError } from '../src/lib/errors'
22
import { STORAGE_KEY } from '../src/lib/constants'
33
import { memoryLocalStorageAdapter } from '../src/lib/local-storage'
44
import GoTrueClient from '../src/GoTrueClient'
@@ -455,11 +455,59 @@ describe('GoTrueClient', () => {
455455
})
456456

457457
test('exchangeCodeForSession() should fail with invalid authCode', async () => {
458-
const { error } = await pkceClient.exchangeCodeForSession('mock_code')
458+
// Mock fetch to return a 400 error for invalid auth code
459+
const mockFetch = jest.fn().mockResolvedValue({
460+
ok: false,
461+
status: 400,
462+
headers: new Headers(),
463+
json: () =>
464+
Promise.resolve({
465+
error: 'invalid_grant',
466+
error_description: 'Invalid auth code',
467+
}),
468+
})
469+
470+
const storage = memoryLocalStorageAdapter()
471+
const client = new GoTrueClient({
472+
url: GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON,
473+
autoRefreshToken: false,
474+
persistSession: true,
475+
storage,
476+
flowType: 'pkce',
477+
fetch: mockFetch,
478+
})
479+
480+
// Set up a code verifier so we can test the invalid auth code error
481+
// @ts-expect-error 'Allow access to protected storageKey'
482+
const storageKey = client.storageKey
483+
await storage.setItem(`${storageKey}-code-verifier`, 'mock-verifier')
484+
485+
const { error } = await client.exchangeCodeForSession('mock_code')
459486

460487
expect(error).not.toBeNull()
461488
expect(error?.status).toEqual(400)
462489
})
490+
491+
test('exchangeCodeForSession() should throw helpful error when code verifier is missing', async () => {
492+
const storage = memoryLocalStorageAdapter()
493+
// Don't set a code verifier - this simulates the common issue where
494+
// the auth flow was initiated in a different browser/device
495+
496+
const client = new GoTrueClient({
497+
url: GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON,
498+
autoRefreshToken: false,
499+
persistSession: true,
500+
storage,
501+
flowType: 'pkce',
502+
})
503+
504+
const { error } = await client.exchangeCodeForSession('some-auth-code')
505+
506+
expect(error).toBeInstanceOf(AuthPKCECodeVerifierMissingError)
507+
expect(error?.message).toContain('PKCE code verifier not found in storage')
508+
expect(error?.message).toContain('@supabase/ssr')
509+
expect(error?.code).toEqual('pkce_code_verifier_not_found')
510+
})
463511
})
464512

465513
describe('Email Auth', () => {

0 commit comments

Comments
 (0)