Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/core/auth-js/src/GoTrueClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2111,6 +2111,14 @@ export default class GoTrueClient {
if (typeof this.detectSessionInUrl === 'function') {
return this.detectSessionInUrl(new URL(window.location.href), params)
}
// Check for Supabase Auth identifier
if ('sb' in params) {
// sb is just an identifier
// Still require OAuth params to prevent forced logout via crafted URLs with only 'sb'
return Boolean(params.access_token || params.error || params.error_description)
}
// TODO @mandarini: Remove this legacy fallback in next major version and return false instead
// Legacy detection for backwards compatibility with older Auth servers that don't include 'sb'
return Boolean(params.access_token || params.error_description)
}

Expand Down
12 changes: 10 additions & 2 deletions packages/core/auth-js/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,23 @@ export type GoTrueClientOptions = {
* The function receives the current URL and parsed parameters, and should return true if the URL
* should be processed as a Supabase auth callback, or false to ignore it.
*
* By default, the client checks for the `sb` parameter (added by Supabase Auth server) to identify
* Supabase callbacks, with a fallback to legacy detection for older Auth server versions.
*
* This is useful when your app uses other OAuth providers (e.g., Facebook Login) that also return
* access_token in the URL fragment, which would otherwise be incorrectly intercepted by Supabase Auth.
*
* @example
* ```ts
* detectSessionInUrl: (url, params) => {
* // Ignore Facebook OAuth redirects
* // Ignore known third-party OAuth paths
* if (url.pathname === '/facebook/redirect') return false
* // Use default detection for other URLs
* // Check for sb identifier (available on newer Auth servers)
* // Still require OAuth params to prevent issues with crafted URLs
* if ('sb' in params) {
* return Boolean(params.access_token || params.error || params.error_description)
* }
* // Fall back to legacy detection for older Auth servers
* return Boolean(params.access_token || params.error_description)
* }
* ```
Expand Down
91 changes: 91 additions & 0 deletions packages/core/auth-js/test/GoTrueClient.browser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,97 @@ describe('Callback URL handling', () => {
expect(data.session).toBeDefined()
expect(data.session?.access_token).toBe('test-token')
})

it('should detect Supabase callback with sb parameter', async () => {
// Simulate Supabase OAuth redirect with sb identifier
window.location.href =
'http://localhost:9999/callback#access_token=test-token&refresh_token=test-refresh&expires_in=3600&token_type=bearer&sb'

// Mock fetch for user info
mockFetch.mockImplementation((url: string) => {
if (url.includes('/user')) {
return Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
id: 'test-user',
email: '[email protected]',
created_at: new Date().toISOString(),
}),
})
}
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) })
})

const client = new (require('../src/GoTrueClient').default)({
url: 'http://localhost:9999',
detectSessionInUrl: true,
autoRefreshToken: false,
storage: mockStorage,
})

await client.initialize()

// Should process the callback because sb parameter is present
const { data } = await client.getSession()
expect(data.session).toBeDefined()
expect(data.session?.access_token).toBe('test-token')
})

it('should detect legacy callbacks without sb parameter for backwards compatibility', async () => {
// Simulate legacy Supabase OAuth redirect without sb identifier
window.location.href =
'http://localhost:9999/callback#access_token=test-token&refresh_token=test-refresh&expires_in=3600&token_type=bearer'

// Mock fetch for user info
mockFetch.mockImplementation((url: string) => {
if (url.includes('/user')) {
return Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
id: 'test-user',
email: '[email protected]',
created_at: new Date().toISOString(),
}),
})
}
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) })
})

const client = new (require('../src/GoTrueClient').default)({
url: 'http://localhost:9999',
detectSessionInUrl: true,
autoRefreshToken: false,
storage: mockStorage,
})

await client.initialize()

// Should still process the callback for backwards compatibility
const { data } = await client.getSession()
expect(data.session).toBeDefined()
expect(data.session?.access_token).toBe('test-token')
})

it('should detect error callbacks with sb parameter', async () => {
// Simulate Supabase OAuth error redirect with sb identifier
window.location.href =
'http://localhost:9999/callback#error=access_denied&error_description=User%20denied%20access&sb'

const client = new (require('../src/GoTrueClient').default)({
url: 'http://localhost:9999',
detectSessionInUrl: true,
autoRefreshToken: false,
storage: mockStorage,
})

const { error } = await client.initialize()

// Should detect and process the error callback
expect(error).toBeDefined()
expect(error?.message).toContain('User denied access')
})
})

describe('GoTrueClient BroadcastChannel', () => {
Expand Down
12 changes: 10 additions & 2 deletions packages/core/supabase-js/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,24 @@ export type SupabaseClientOptions<SchemaName> = {
* a Supabase auth callback. The function receives the current URL and parsed parameters,
* and should return true if the URL should be processed as a Supabase auth callback.
*
* By default, the client checks for the `sb` parameter (added by Supabase Auth server) to identify
* Supabase callbacks, with a fallback to legacy detection for older Auth server versions.
*
* This is useful when your app uses other OAuth providers (e.g., Facebook Login) that
* also return access_token in the URL fragment, which would otherwise be incorrectly
* intercepted by Supabase Auth.
*
* @example
* ```ts
* detectSessionInUrl: (url, params) => {
* // Ignore Facebook OAuth redirects
* // Ignore known third-party OAuth paths
* if (url.pathname === '/facebook/redirect') return false
* // Use default detection for other URLs
* // Check for sb identifier (available on newer Auth servers)
* // Still require OAuth params to prevent issues with crafted URLs
* if ('sb' in params) {
* return Boolean(params.access_token || params.error || params.error_description)
* }
* // Fall back to legacy detection for older Auth servers
* return Boolean(params.access_token || params.error_description)
* }
* ```
Expand Down
Loading