new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Supabase · Auth · all subjects

oauth providers/custom

109 notes in this subject, read out of this brain and free to use. This is page 1 of 2.

User sign-in with custom provider via Flutter

Users can sign in with a custom provider using the Supabase Flutter client by calling `await supabase.auth.signInWithOAuth(OAuthProvider('custom:my-provider'))`

Custom provider quota by plan

Free plan projects can add up to 3 custom providers. Pro plan and above have unlimited custom providers.

Callback URL for custom providers

When creating a custom provider, the creation form displays a read-only Callback URL that must be copied and configured as the redirect/callback URI in the external identity provider before completing setup.

Custom OAuth/OIDC provider types

There are two types of custom providers: OAuth2 for generic OAuth2 providers where you supply authorization, token, and userinfo endpoint URLs manually; and OIDC for providers supporting OpenID Connect discovery where you supply only the issuer URL and endpoints are resolved automatically.

OAuth2 provider setup via JavaScript API

To create an OAuth2 custom provider via JavaScript, call `supabase.auth.admin.customProviders.createProvider()` with parameters: `provider_type: 'oauth2'`, `identifier` (starting with `custom:`), `name`, `client_id`, `client_secret`, `authorization_url`, `token_url`, `userinfo_url`, and optionally `scopes` as an array of strings.

OIDC provider setup via JavaScript API

To create an OIDC custom provider via JavaScript, call `supabase.auth.admin.customProviders.createProvider()` with parameters: `provider_type: 'oidc'`, `identifier` (starting with `custom:`), `name`, `client_id`, `client_secret`, `issuer` URL, and optionally `scopes` as an array (the `openid` scope is automatically included if missing).

OIDC automatic behavior

OIDC providers automatically fetch the discovery document from `{issuer}/.well-known/openid-configuration` (or from `discovery_url` if set), always include the `openid` scope (added automatically if missing), and verify ID tokens against the provider's JWKS fetched from the discovery document's `jwks_uri`.

User sign-in with custom provider via OAuth endpoint

Users sign in via the standard OAuth authorize endpoint: `GET https://your-project.supabase.co/auth/v1/authorize?provider=custom:my-provider`

User sign-in with custom provider via JavaScript client

Users can sign in with a custom provider using the Supabase JavaScript client by calling `supabase.auth.signInWithOAuth({ provider: 'custom:my-provider' })`

User sign-in with custom provider via Swift

Users can sign in with a custom provider using the Supabase Swift client by calling `try await supabase.auth.signInWithOAuth(provider: "custom:my-provider", redirectTo: URL(string: "my-custom-scheme://my-app-host"))`

User sign-in with custom provider via Kotlin

Users can sign in with a custom provider using the Supabase Kotlin client by calling `supabase.auth.signInWith(CustomProvider("custom:my-provider"))`

List custom providers via JavaScript API

List all custom providers using `supabase.auth.admin.customProviders.listProviders()`. Optionally filter by provider type with `{ type: 'oidc' }` or `{ type: 'oauth2' }`.

Update custom provider via JavaScript API

Update a custom provider using `supabase.auth.admin.customProviders.updateProvider('custom:provider-id', { /* fields to update */ })`. Cannot update `provider_type` or `identifier`. Only provided fields are changed (partial update). To rotate a client secret, update only the `client_secret` field.

Delete custom provider via JavaScript API

Delete a custom provider using `supabase.auth.admin.customProviders.deleteProvider('custom:provider-id')`

PKCE enabled by default for custom providers

PKCE (Proof Key for Code Exchange) is enabled by default (`pkce_enabled: true`) for all custom providers. The auth server automatically generates a code challenge and verifier during the authorization flow, protecting against authorization code interception attacks. This is handled entirely server-side with no client-side PKCE logic needed. PKCE can be disabled by setting `pkce_enabled: false` when creating or updating a provider, though this is not recommended unless the identity provider does not support PKCE.

Authorization params for custom providers

Extra query parameters can be appended to the provider's authorization URL during the OAuth flow via authorization params. All values must be strings. Example parameters include `prompt`, `access_type`, and `login_hint`. The following reserved parameters are managed by the auth server and cannot be overridden: `client_id`, `client_secret`, `redirect_uri`, `response_type`, `state`, `code_challenge`, `code_challenge_method`, `code_verifier`, `nonce`.

Multi-platform apps with custom providers

For apps using different client IDs for different platforms (web vs mobile), use the `acceptable_client_ids` parameter when creating or updating a provider to list additional client IDs that should be accepted for audience validation in OIDC ID tokens.

Email-optional custom providers

By default, custom providers must return an email address. Set `email_optional` to `true` when creating or updating a provider to allow sign-in without an email. This applies to both OAuth2 and OIDC providers.

OIDC-specific provider options

OIDC providers support these options: `discovery_url` (string, default null) to override the discovery document URL if the provider uses a non-standard location; `skip_nonce_check` (boolean, default false) to skip nonce validation on ID tokens for providers that do not support nonce.

Custom provider error codes

Error codes for custom provider operations: `validation_failed` (HTTP 400) for invalid parameters, missing required fields, bad format, reserved params, or invalid URLs; `conflict` (HTTP 400) when a provider with the same identifier already exists; `over_custom_provider_quota` (HTTP 400) when maximum number of custom providers is reached; `custom_provider_not_found` (HTTP 404) when no provider exists with the given identifier.

Custom OAuth/OIDC provider identifier format

Custom provider identifiers must start with the `custom:` prefix. Identifiers are 2–50 characters long, lowercase alphanumeric with hyphens and colons allowed. Examples include `custom:my-provider` and `custom:github-enterprise`. This prefix distinguishes custom providers from built-in providers.

Example: List OAuth clients with cURL for local development

curl 'http://localhost:54321/auth/v1/admin/oauth/clients' \ -H "Authorization: Bearer ${SUPABASE_SECRET_KEY}" This example shows how to list OAuth clients via cURL for local development Supabase instances.

OAuth redirect URI security requirements

OAuth client redirect URIs require exact, complete URL matches. Unlike general redirect URLs which support wildcards, OAuth client redirect URIs do NOT support wildcards, patterns, or partial URLs. You must register the full, exact callback URL including protocol, domain, path, and port if needed.

Customize OAuth access tokens with hooks

By default, OAuth access tokens include standard claims like user_id, role, and client_id. To customize tokens, use Custom Access Token Hooks which are triggered for all token issuance including OAuth flows. You can use the client_id parameter to customize tokens based on which OAuth client is requesting them. Common use cases include customizing the audience claim to the third-party API endpoint for proper JWT validation, adding client-specific permissions via custom claims based on which OAuth client is requesting access, and implementing dynamic scopes with metadata for RLS policies.

OAuth 2.1 server endpoints for cloud projects

Supabase cloud projects expose these OAuth 2.1 endpoints: Authorization endpoint at https://<project-ref>.supabase.co/auth/v1/oauth/authorize, Token endpoint at https://<project-ref>.supabase.co/auth/v1/oauth/token, JWKS endpoint at https://<project-ref>.supabase.co/auth/v1/.well-known/jwks.json, Discovery endpoint at https://<project-ref>.supabase.co/.well-known/oauth-authorization-server/auth/v1, and OIDC discovery at https://<project-ref>.supabase.co/auth/v1/.well-known/openid-configuration.

OAuth 2.1 server endpoints for local development

Local Supabase instances expose these OAuth 2.1 endpoints: Authorization endpoint at http://localhost:54321/auth/v1/oauth/authorize, Token endpoint at http://localhost:54321/auth/v1/oauth/token, JWKS endpoint at http://localhost:54321/auth/v1/.well-known/jwks.json, Discovery endpoint at http://localhost:54321/.well-known/oauth-authorization-server/auth/v1, and OIDC discovery at http://localhost:54321/auth/v1/.well-known/openid-configuration.

Enable OAuth 2.1 server in cloud dashboard

To enable OAuth 2.1 server on a Supabase cloud project, go to the project dashboard, navigate to Authentication > OAuth Server in the sidebar, and enable OAuth 2.1 server capabilities. OAuth 2.1 server is currently in beta and free to use during the beta period on all Supabase plans.

Enable OAuth 2.1 server in local development with CLI

To enable OAuth 2.1 server for local development, edit supabase/config.toml and add [auth.oauth_server] section with enabled = true and authorization_url_path = "/oauth/consent". Optionally set allow_dynamic_registration = false to disable dynamic client registration. Then start or restart the local Supabase instance with 'supabase start' or 'supabase stop && supabase start'.

Configure JWT issuer for tunneled local development

When exposing a local Supabase instance via tunnel (using ngrok or Cloudflare Tunnel), configure the jwt_issuer field in supabase/config.toml to match your tunnel URL (e.g., https://my-tunnel.url/auth/v1). This ensures JWTs issued by the local instance use the correct issuer claim for token validation and serves the discovery endpoint at the correct location.

Authorization path configuration

The authorization path is combined with the Site URL (configured in Authentication > URL Configuration) to create the full authorization endpoint URL where users are redirected during OAuth flows. For example, if Site URL is https://example.com and authorization path is /oauth/consent, the authorization UI will be at https://example.com/oauth/consent. Configure the authorization path in the project dashboard under Authentication > OAuth Server.

Client secret security and regeneration

Store the client secret securely when registering an OAuth client. The secret will only be shown once. If you lose it, you can regenerate a new one from the OAuth Apps page in the dashboard.

OAuth authorization flow request parameters

When OAuth clients initiate the authorization flow, Supabase Auth redirects users to the configured authorization path with an authorization_id query parameter. The authorization UI receives this authorization_id to retrieve client details and authorization scope information.

Supabase OAuth authorization methods in JavaScript SDK

The Supabase JavaScript library provides these OAuth methods for handling authorization: supabase.auth.oauth.getAuthorizationDetails(authorization_id) retrieves client and authorization details, supabase.auth.oauth.approveAuthorization(authorization_id) approves the authorization request, and supabase.auth.oauth.denyAuthorization(authorization_id) denies the authorization request.

Authorization UI implementation steps

To build an authorization UI, extract the authorization_id from URL query parameters, authenticate the user (redirect to login if needed, preserving the authorization_id), retrieve authorization details using supabase.auth.oauth.getAuthorizationDetails(authorization_id), display a consent screen showing the requesting app name and requested scopes, and handle the user's decision by calling either approveAuthorization(authorization_id) or denyAuthorization(authorization_id) based on user choice.

OAuth authorization details response structure

The getAuthorizationDetails response includes client information and a scope field containing a space-separated string of scopes requested by the client (e.g., "openid email profile"). If the authorization_id is not present in the response, the user has previously consented and should be redirected using the redirect_url included in the response.

OAuth approval/denial redirect flow

After calling approveAuthorization or denyAuthorization, these methods return a redirect_url. For approved requests, this URL includes an authorization code. For denied requests, it includes an error. The user should be redirected to this redirect_url to send them back to the third-party app.

OAuth redirect URI best practices

Use HTTPS in production for all redirect URIs. Register exact, complete URLs as each redirect URI must be the full URL including protocol, domain, path, and port if needed. Use separate OAuth clients per environment (development, staging, production) to provide better security isolation, allow independent secret rotation, and improve auditability. If needing to use the same client across environments, you can register multiple redirect URIs, but separate clients are recommended.

Example: Next.js OAuth consent page implementation

// app/oauth/consent/page.tsx import { createServerClient } from '@supabase/ssr' import { cookies } from 'next/headers' import { redirect } from 'next/navigation' export default async function ConsentPage({ searchParams, }: { searchParams: { authorization_id?: string } }) { const authorizationId = (await searchParams).authorization_id if (!authorizationId) { return <div>Error: Missing authorization_id</div> } const supabase = createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!, { cookies: { getAll: async () => (await cookies()).getAll(), setAll: async (cookiesToSet, _headers) => { const cookieStore = await cookies() cookiesToSet.forEach(({ name, value, options }) => cookieStore.set(name, value, options)) }, }, } ) // Check if user is authenticated const { data } = await supabase.auth.getClaims() const claims = data?.claims if (!claims) { // Redirect to login, preserving authorization_id redirect(`/login?redirect=/oauth/consent?authorization_id=${authorizationId}`) } // Get authorization details using the authorization_id const { data: authDetails, error } = await supabase.auth.oauth.getAuthorizationDetails(authorizationId) if (error || !authDetails) { return <div>Error: {error?.message || 'Invalid authorization request'}</div> } // if no authorization_id returned, user has previously consented, redirect them if (!('authorization_id' in authDetails)) { redirect(authDetails['redirect_url']) } return ( <div> <h1>Authorize {authDetails.client.name}</h1> <p>This application wants to access your account.</p> <div> <p> <strong>Client:</strong> {authDetails.client.name} </p> <p> <strong>Redirect URI:</strong> {authDetails.redirect_uri} </p> {authDetails.scope && authDetails.scope.trim() && ( <div> <strong>Requested permissions:</strong> <ul> {authDetails.scope.split(' ').map((scopeItem) => ( <li key={scopeItem}>{scopeItem}</li> ))} </ul> </div> )} </div> <form action="/api/oauth/decision" method="POST"> <input type="hidden" name="authorization_id" value={authorizationId} /> <button type="submit" name="decision" value="approve"> Approve </button> <button type="submit" name="decision" value="deny"> Deny </button> </form> </div> ) } This example shows a Next.js server component that checks user authentication, retrieves authorization details, and displays a consent screen for OAuth flows.

Example: Next.js OAuth decision handler API route

// app/api/oauth/decision/route.ts import { createServerClient } from '@supabase/ssr' import { cookies } from 'next/headers' import { NextResponse } from 'next/server' export async function POST(request: Request) { const formData = await request.formData() const decision = formData.get('decision') const authorizationId = formData.get('authorization_id') as string if (!authorizationId) { return NextResponse.json({ error: 'Missing authorization_id' }, { status: 400 }) } const supabase = createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!, { cookies: { getAll: async () => (await cookies()).getAll(), setAll: async (cookiesToSet, _headers) => { const cookieStore = await cookies() cookiesToSet.forEach(({ name, value, options }) => cookieStore.set(name, value, options)) }, }, } ) if (decision === 'approve') { const { data, error } = await supabase.auth.oauth.approveAuthorization(authorizationId) if (error) { return NextResponse.json({ error: error.message }, { status: 400 }) } // Redirect back to the client with authorization code return NextResponse.redirect(data.redirect_url) } else { const { data, error } = await supabase.auth.oauth.denyAuthorization(authorizationId) if (error) { return NextResponse.json({ error: error.message }, { status: 400 }) } // Redirect back to the client with error return NextResponse.redirect(data.redirect_url) } } This example shows how to handle OAuth approval/denial decisions in a Next.js API route.

Example: React SPA OAuth consent page

// src/pages/OAuthConsent.tsx import { useEffect, useState } from 'react' import { useNavigate, useSearchParams } from 'react-router-dom' import { supabase } from './supabaseClient' export function OAuthConsent() { const navigate = useNavigate() const [searchParams] = useSearchParams() const authorizationId = searchParams.get('authorization_id') const [authDetails, setAuthDetails] = useState<any>(null) const [loading, setLoading] = useState(true) const [error, setError] = useState<string | null>(null) useEffect(() => { async function loadAuthDetails() { if (!authorizationId) { setError('Missing authorization_id') setLoading(false) return } // Check if user is authenticated const { data: { user }, } = await supabase.auth.getUser() if (!user) { navigate(`/login?redirect=/oauth/consent?authorization_id=${authorizationId}`) return } // Get authorization details using the authorization_id const { data, error } = await supabase.auth.oauth.getAuthorizationDetails(authorizationId) if (error) { setError(error.message) } else { setAuthDetails(data) } setLoading(false) } loadAuthDetails() }, [authorizationId, navigate]) async function handleApprove() { if (!authorizationId) return const { data, error } = await supabase.auth.oauth.approveAuthorization(authorizationId) if (error) { setError(error.message) } else { // Redirect to client app window.location.href = data.redirect_url } } async function handleDeny() { if (!authorizationId) return const { data, error } = await supabase.auth.oauth.denyAuthorization(authorizationId) if (error) { setError(error.message) } else { // Redirect to client app with error window.location.href = data.redirect_url } } if (loading) return <div>Loading...</div> if (error) return <div>Error: {error}</div> if (!authDetails) return <div>No authorization request found</div> return ( <div> <h1>Authorize {authDetails.client.name}</h1> <p>This application wants to access your account.</p> <div> <p> <strong>Client:</strong> {authDetails.client.name} </p> <p> <strong>Redirect URI:</strong> {authDetails.redirect_uri} </p> {authDetails.scope && authDetails.scope.trim() && ( <div> <strong>Requested permissions:</strong> <ul> {authDetails.scope.split(' ').map((scopeItem) => ( <li key={scopeItem}>{scopeItem}</li> ))} </ul> </div> )} </div> <div> <button onClick={handleApprove}>Approve</button> <button onClick={handleDeny}>Deny</button> </div> </div> ) } This example shows a React SPA implementation of an OAuth consent page with approval/denial handling.

Register OAuth client in dashboard

To register an OAuth client in the Supabase dashboard, go to Authentication > OAuth Apps (under the Manage section), click Add a new client, enter the client name, one or more redirect URIs, and select the client type (Public for mobile and single-page apps without client secret, or Confidential for server-side apps with client secret). Click Create to receive the Client ID and Client Secret (for confidential clients).

OAuth client types and token endpoint auth methods

Token endpoint authentication method controls how clients authenticate when exchanging an authorization code or refreshing a token. Method 'none' means no client authentication, only client_id is sent in request body (required for public clients). Method 'client_secret_basic' sends client credentials via HTTP Basic auth as Authorization: Basic <base64(client_id:client_secret)> (default for confidential clients per RFC 7591). Method 'client_secret_post' sends client credentials in request body as client_id and client_secret form parameters. Public clients default to 'none' and must use 'none'. Confidential clients default to 'client_secret_basic' and cannot use 'none'.

Example: Create OAuth client with JavaScript SDK

import { createClient } from '@supabase/supabase-js' const supabase = createClient( 'https://your-project-id.supabase.co', 'sb_secret_...' // Use the secret key for admin operations ) // Create an OAuth client const { data, error } = await supabase.auth.admin.oauth.createClient({ name: 'My Third-Party App', redirect_uris: ['https://my-app.com/auth/callback', 'https://my-app.com/auth/silent-callback'], client_type: 'confidential', // Optional: defaults to 'client_secret_basic' for confidential, 'none' for public token_endpoint_auth_method: 'client_secret_basic', }) if (error) { console.error('Error creating client:', error) } else { console.log('Client created:', data) console.log('Client ID:', data.client_id) console.log('Client Secret:', data.client_secret) // Store this securely! } This example shows how to create an OAuth client programmatically using the Supabase JavaScript SDK.

Example: List OAuth clients with JavaScript SDK

const { data, error } = await supabase.auth.admin.oauth.listClients() if (error) { console.error('Error listing clients:', error) } else { console.log('OAuth clients:', data) } This example shows how to list all registered OAuth clients using the Supabase JavaScript SDK.

Example: Create OAuth client with cURL for production

curl -X POST 'https://<project-ref>.supabase.co/auth/v1/admin/oauth/clients' \ -H "Authorization: Bearer ${SUPABASE_SECRET_KEY}" \ -H "Content-Type: application/json" \ -d '{ "name": "My Third-Party App", "redirect_uris": [ "https://my-app.com/auth/callback", "https://my-app.com/auth/silent-callback" ], "client_type": "confidential", "token_endpoint_auth_method": "client_secret_basic" }' Response: { "client_id": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d", "client_secret": "verysecret-1234567890abcdef...", "name": "My Third-Party App", "redirect_uris": ["https://my-app.com/auth/callback", "https://my-app.com/auth/silent-callback"], "client_type": "confidential", "token_endpoint_auth_method": "client_secret_basic", "created_at": "2025-01-15T10:30:00.000Z" } This example shows how to create an OAuth client via cURL for production Supabase projects.

Example: Create OAuth client with cURL for local development

curl -X POST 'http://localhost:54321/auth/v1/admin/oauth/clients' \ -H "Authorization: Bearer ${SUPABASE_SECRET_KEY}" \ -H "Content-Type: application/json" \ -d '{ "name": "Local Dev App", "redirect_uris": ["http://localhost:3000/auth/callback"], "client_type": "confidential", "token_endpoint_auth_method": "client_secret_post" }' This example shows how to create an OAuth client via cURL for local development Supabase instances.

Example: List OAuth clients with cURL for production

curl 'https://<project-ref>.supabase.co/auth/v1/admin/oauth/clients' \ -H "Authorization: Bearer ${SUPABASE_SECRET_KEY}" This example shows how to list OAuth clients via cURL for production Supabase projects.

Flutter Sign in with Apple on iOS and macOS

Use the sign_in_with_apple package on Flutter for iOS and macOS. Follow package README for setup. Generate raw nonce, hash it, call SignInWithApple.getAppleIDCredential with scopes email and fullName, then pass the identity token to supabase.auth.signInWithIdToken with provider 'apple' and nonce. Full name available only on first sign-in; save via updateUser.

Secret key rotation required every 6 months for Apple OAuth

If using the OAuth flow (web, Flutter web, Kotlin non-iOS platforms), Apple requires generating a new secret key every 6 months using the signing key (.p8 file). This is critical maintenance that will cause authentication failures if missed. Set a recurring calendar reminder, store the .p8 file securely, and if lost or compromised, immediately revoke it in the Apple Developer Console and create a new one. Native-only implementations do not require secret key rotation.

Apple identity token does not include full name

Apple's identity token does not include the user's full name in its claims. The full name is only provided during the first sign-in attempt when the user initially authorizes the app. All subsequent sign-ins return null for full name fields. The full name must be captured from Apple's native authentication response and manually saved using the updateUser method.

Handling Apple full name on first sign-in

After successful Sign in with Apple, check if the full name is available in the authentication response. If provided, use the updateUser method to save it to user metadata with fields: full_name, given_name, and family_name. This approach works for both OAuth and native flows.

Web OAuth flow configuration requirements

To configure Apple OAuth on web, you need: (1) Team ID (10-character alphanumeric string from Apple Developer Console); (2) registered email sources in Services section for Sign in with Apple for Email Communication; (3) App ID (reverse domain name like com.example.app, configured with Sign in with Apple capability); (4) Services ID (reverse domain name like com.example.app.web); (5) Website URLs configured for the Services ID with domain like <project-id>.supabase.co and redirect URL https://<project-id>.supabase.co/auth/v1/callback; (6) signing Key (.p8 file) from Keys section used to generate secret; (7) Services ID registered in Supabase dashboard. If using both web OAuth and native sign-in, list the Services ID as the first entry in Client IDs field.

Management API configuration for Apple auth provider

Apple auth provider can be configured via Management API using PATCH request to https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth with Authorization header and Content-Type application/json. Required fields: external_apple_enabled (boolean), external_apple_client_id (string, Services ID), external_apple_secret (string, generated secret key). Access token obtained from https://supabase.com/dashboard/account/tokens.

signInWithOAuth example for Apple on web

To initiate Apple sign-in on web using OAuth flow, call supabase.auth.signInWithOAuth({ provider: 'apple' }). This redirects to Apple's consent screen. After completion, user profile is exchanged and validated, then redirects back with access and refresh tokens.

Sign in with Apple JS not accessible in OAuth flow

When using the OAuth flow, the user's full name is not accessible from Apple's response. Full name is only available through native authentication methods during first sign-in. Consider alternatives: use Sign in with Apple JS instead, collect name through separate onboarding form, or use a profiles table.

Sign in with Apple JS configuration for websites

To use Sign in with Apple JS, configure: (1) App ID with Sign in with Apple capability; (2) Services ID for the website (reverse domain name like com.example.app.website); (3) Website URLs with domain and callback URL; (4) Register Services ID in Supabase dashboard under Client IDs. If using Sign in with Apple JS only, OAuth settings are not needed.

signInWithIdToken example using Sign in with Apple JS

After AppleID.auth.signIn(), use supabase.auth.signInWithIdToken({ provider: 'apple', token: data.id_token, nonce: nonce }) to exchange the ID token for Supabase session. Full name is available from data.user.name only on first sign-in. After successful sign-in, use updateUser to save full_name, given_name, and family_name to metadata if available.

Sign in with Apple JS event listener setup

Initialize AppleID.auth with clientId, scope, redirectURI, usePopup, and nonce. Listen for 'AppleIDSignInOnSuccess' event. On success, extract id_token and user info from event.detail.authorization and event.detail.user. Call signInWithIdToken with provider 'apple', token, and nonce. Full name only available on first sign-in from event.detail.user.name.

Expo native Apple authentication on iOS

When working with Expo on iOS, use Expo AppleAuthentication library to obtain ID token, then pass to supabase-js signInWithIdToken method with provider 'apple'. Request scopes FULL_NAME and EMAIL. Full name available from credential.fullName only on first sign-in; save to metadata using updateUser with full_name, given_name, family_name.

Expo Android does not support native Apple authentication

Sign in with Apple is not natively available on Android devices. Use signInWithOAuth flow instead, which opens a browser window. Configure redirectTo as 'your-app-scheme://auth/callback' and set skipBrowserRedirect to false. Requires deep linking configuration.

Give your agent this brain