Skip to content

Commit 34374c6

Browse files
committed
feat(auth): add support for Supabase Auth sb identifier
1 parent efca12c commit 34374c6

File tree

4 files changed

+110
-4
lines changed

4 files changed

+110
-4
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2111,6 +2111,11 @@ export default class GoTrueClient {
21112111
if (typeof this.detectSessionInUrl === 'function') {
21122112
return this.detectSessionInUrl(new URL(window.location.href), params)
21132113
}
2114+
// Check for Supabase Auth identifier
2115+
// Fall back to legacy detection for backwards compatibility with older Auth servers
2116+
if ('sb' in params) {
2117+
return true
2118+
}
21142119
return Boolean(params.access_token || params.error_description)
21152120
}
21162121

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,20 @@ export type GoTrueClientOptions = {
8484
* The function receives the current URL and parsed parameters, and should return true if the URL
8585
* should be processed as a Supabase auth callback, or false to ignore it.
8686
*
87+
* By default, the client checks for the `sb` parameter (added by Supabase Auth server) to identify
88+
* Supabase callbacks, with a fallback to legacy detection for older Auth server versions.
89+
*
8790
* This is useful when your app uses other OAuth providers (e.g., Facebook Login) that also return
8891
* access_token in the URL fragment, which would otherwise be incorrectly intercepted by Supabase Auth.
8992
*
9093
* @example
9194
* ```ts
9295
* detectSessionInUrl: (url, params) => {
93-
* // Ignore Facebook OAuth redirects
96+
* // Prefer sb identifier (available on newer Auth servers)
97+
* if ('sb' in params) return true
98+
* // Ignore known third-party OAuth paths
9499
* if (url.pathname === '/facebook/redirect') return false
95-
* // Use default detection for other URLs
100+
* // Fall back to legacy detection for older Auth servers
96101
* return Boolean(params.access_token || params.error_description)
97102
* }
98103
* ```

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

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,97 @@ describe('Callback URL handling', () => {
580580
expect(data.session).toBeDefined()
581581
expect(data.session?.access_token).toBe('test-token')
582582
})
583+
584+
it('should detect Supabase callback with sb parameter', async () => {
585+
// Simulate Supabase OAuth redirect with sb identifier
586+
window.location.href =
587+
'http://localhost:9999/callback#access_token=test-token&refresh_token=test-refresh&expires_in=3600&token_type=bearer&sb'
588+
589+
// Mock fetch for user info
590+
mockFetch.mockImplementation((url: string) => {
591+
if (url.includes('/user')) {
592+
return Promise.resolve({
593+
ok: true,
594+
json: () =>
595+
Promise.resolve({
596+
id: 'test-user',
597+
598+
created_at: new Date().toISOString(),
599+
}),
600+
})
601+
}
602+
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) })
603+
})
604+
605+
const client = new (require('../src/GoTrueClient').default)({
606+
url: 'http://localhost:9999',
607+
detectSessionInUrl: true,
608+
autoRefreshToken: false,
609+
storage: mockStorage,
610+
})
611+
612+
await client.initialize()
613+
614+
// Should process the callback because sb parameter is present
615+
const { data } = await client.getSession()
616+
expect(data.session).toBeDefined()
617+
expect(data.session?.access_token).toBe('test-token')
618+
})
619+
620+
it('should detect legacy callbacks without sb parameter for backwards compatibility', async () => {
621+
// Simulate legacy Supabase OAuth redirect without sb identifier
622+
window.location.href =
623+
'http://localhost:9999/callback#access_token=test-token&refresh_token=test-refresh&expires_in=3600&token_type=bearer'
624+
625+
// Mock fetch for user info
626+
mockFetch.mockImplementation((url: string) => {
627+
if (url.includes('/user')) {
628+
return Promise.resolve({
629+
ok: true,
630+
json: () =>
631+
Promise.resolve({
632+
id: 'test-user',
633+
634+
created_at: new Date().toISOString(),
635+
}),
636+
})
637+
}
638+
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) })
639+
})
640+
641+
const client = new (require('../src/GoTrueClient').default)({
642+
url: 'http://localhost:9999',
643+
detectSessionInUrl: true,
644+
autoRefreshToken: false,
645+
storage: mockStorage,
646+
})
647+
648+
await client.initialize()
649+
650+
// Should still process the callback for backwards compatibility
651+
const { data } = await client.getSession()
652+
expect(data.session).toBeDefined()
653+
expect(data.session?.access_token).toBe('test-token')
654+
})
655+
656+
it('should detect error callbacks with sb parameter', async () => {
657+
// Simulate Supabase OAuth error redirect with sb identifier
658+
window.location.href =
659+
'http://localhost:9999/callback#error=access_denied&error_description=User%20denied%20access&sb'
660+
661+
const client = new (require('../src/GoTrueClient').default)({
662+
url: 'http://localhost:9999',
663+
detectSessionInUrl: true,
664+
autoRefreshToken: false,
665+
storage: mockStorage,
666+
})
667+
668+
const { error } = await client.initialize()
669+
670+
// Should detect and process the error callback
671+
expect(error).toBeDefined()
672+
expect(error?.message).toContain('access_denied')
673+
})
583674
})
584675

585676
describe('GoTrueClient BroadcastChannel', () => {

packages/core/supabase-js/src/lib/types.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,21 @@ export type SupabaseClientOptions<SchemaName> = {
5353
* a Supabase auth callback. The function receives the current URL and parsed parameters,
5454
* and should return true if the URL should be processed as a Supabase auth callback.
5555
*
56+
* By default, the client checks for the `sb` parameter (added by Supabase Auth server) to identify
57+
* Supabase callbacks, with a fallback to legacy detection for older Auth server versions.
58+
*
5659
* This is useful when your app uses other OAuth providers (e.g., Facebook Login) that
5760
* also return access_token in the URL fragment, which would otherwise be incorrectly
5861
* intercepted by Supabase Auth.
5962
*
6063
* @example
6164
* ```ts
6265
* detectSessionInUrl: (url, params) => {
63-
* // Ignore Facebook OAuth redirects
66+
* // Prefer sb identifier (available on newer Auth servers)
67+
* if ('sb' in params) return true
68+
* // Ignore known third-party OAuth paths
6469
* if (url.pathname === '/facebook/redirect') return false
65-
* // Use default detection for other URLs
70+
* // Fall back to legacy detection for older Auth servers
6671
* return Boolean(params.access_token || params.error_description)
6772
* }
6873
* ```

0 commit comments

Comments
 (0)