Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add third-party auth support #1004

Merged
merged 1 commit into from
Jul 29, 2024
Merged
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
34 changes: 28 additions & 6 deletions src/SupabaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export default class SupabaseClient<
protected storageKey: string
protected fetch?: Fetch
protected changedAccessToken?: string
protected accessToken?: () => Promise<string>

protected headers: Record<string, string>

Expand Down Expand Up @@ -95,11 +96,26 @@ export default class SupabaseClient<
this.storageKey = settings.auth.storageKey ?? ''
this.headers = settings.global.headers ?? {}

this.auth = this._initSupabaseAuthClient(
settings.auth ?? {},
this.headers,
settings.global.fetch
)
if (!settings.accessToken) {
this.auth = this._initSupabaseAuthClient(
settings.auth ?? {},
this.headers,
settings.global.fetch
)
} else {
this.accessToken = settings.accessToken

this.auth = new Proxy<SupabaseAuthClient>({} as any, {
get: (_, prop) => {
throw new Error(
`@supabase/supabase-js: Supabase Client is configured with the accessToken option, accessing supabase.auth.${String(
prop
)} is not possible`
)
},
})
}

this.fetch = fetchWithAuth(supabaseKey, this._getAccessToken.bind(this), settings.global.fetch)

this.realtime = this._initRealtimeClient({ headers: this.headers, ...settings.realtime })
Expand All @@ -109,7 +125,9 @@ export default class SupabaseClient<
fetch: this.fetch,
})

this._listenForAuthEvents()
if (!settings.accessToken) {
this._listenForAuthEvents()
}
}

/**
Expand Down Expand Up @@ -244,6 +262,10 @@ export default class SupabaseClient<
}

private async _getAccessToken() {
if (this.accessToken) {
return await this.accessToken()
}

const { data } = await this.auth.getSession()

return data.session?.access_token ?? null
Expand Down
12 changes: 11 additions & 1 deletion src/lib/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function applySettingDefaults<
global: DEFAULT_GLOBAL_OPTIONS,
} = defaults

return {
const result: Required<SupabaseClientOptions<SchemaName>> = {
db: {
...DEFAULT_DB_OPTIONS,
...dbOptions,
Expand All @@ -54,5 +54,15 @@ export function applySettingDefaults<
...DEFAULT_GLOBAL_OPTIONS,
...globalOptions,
},
accessToken: async () => '',
}

if (options.accessToken) {
result.accessToken = options.accessToken
} else {
// hack around Required<>
delete (result as any).accessToken
}

return result
}
12 changes: 12 additions & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ export type SupabaseClientOptions<SchemaName> = {
*/
headers?: Record<string, string>
}
/**
* Optional function for using a third-party authentication system with
* Supabase. The function should return an access token or ID token (JWT) by
* obtaining it from the third-party auth client library. Note that this
* function may be called concurrently and many times. Use memoization and
* locking techniques if this is not supported by the client libraries.
*
* When set, the `auth` namespace of the Supabase client cannot be used.
* Create another client if you wish to use Supabase Auth and third-party
* authentications concurrently in the same application.
*/
accessToken?: () => Promise<string>
}

export type GenericTable = {
Expand Down
12 changes: 12 additions & 0 deletions test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ const KEY = 'some.fake.key'

const supabase = createClient(URL, KEY)

test('it should create a client with third-party auth accessToken', async () => {
const client = createClient(URL, KEY, {
accessToken: async () => {
return 'jwt'
},
})

expect(() => client.auth.getUser()).toThrowError(
'@supabase/supabase-js: Supabase Client is configured with the accessToken option, accessing supabase.auth.getUser is not possible'
)
})

test('it should create the client connection', async () => {
expect(supabase).toBeDefined()
expect(supabase).toBeInstanceOf(SupabaseClient)
Expand Down
Loading