Apple Sign-In Integration
Sign in with Apple (SIWA) lets users authenticate using their Apple ID via the OIDC-based Apple identity provider. ButtrBase handles the OIDC flow, token exchange, and user provisioning so you only need to configure the connection and redirect users.
Prerequisites
Before configuring SIWA in ButtrBase, complete these steps in the Apple Developer portal:
1. App ID: Enable the "Sign In with Apple" capability for your App ID.
2. Service ID: Create a Services ID (this is your OIDC client ID). Set the description and identifier (e.g. com.acme.app.siwa).
3. Return URL: Add your ButtrBase callback URL as a return URL in the Service ID configuration:
~~~
https://api.buttrbase.com/auth/oidc/apple/callback
~~~
4. Key: Create a new key with "Sign In with Apple" enabled. Download the .p8 private key file — it can only be downloaded once.
5. Note your: Team ID, Key ID, Service ID (client ID), and the .p8 key contents.
Configure the OIDC Connection
Create an Apple OIDC connection in your ButtrBase organization:
~~~json
POST /api/admin/organizations/:org_uuid/oidc-connections
Authorization: Bearer
{ "name": "Apple Sign In", "provider": "apple", "client_id": "com.acme.app.siwa", "team_id": "ABCDE12345", "key_id": "FGHIJ67890", "private_key_pem": "-----BEGIN PRIVATE KEY----- MIGH... -----END PRIVATE KEY-----", "allowed_domains": [] } ~~~
| Field | Description |
|-------|-------------|
| client_id | Your Apple Service ID identifier |
| team_id | Your 10-character Apple Developer Team ID |
| key_id | The Key ID from the .p8 key you created |
| private_key_pem | The contents of your .p8 private key file |
| allowed_domains | Optional: restrict to users with matching email domains |
Response:
~~~json { "connection_uuid": "conn-apple-uuid-123", "redirect_uri": "https://api.buttrbase.com/auth/oidc/conn-apple-uuid-123/callback" } ~~~
Note: ButtrBase generates a client_secret JWT from your private key automatically (as required by Apple's non-standard OIDC implementation). You do not need to manage the client secret manually.
Login Flow
Redirect the user to Apple
Construct the authorization URL and redirect:
~~~ GET https://api.buttrbase.com/auth/oidc/conn-apple-uuid-123/authorize?redirect_uri=https://myapp.com/auth/callback&state=random-state-value ~~~
The user is sent to Apple's login page. After authenticating, Apple redirects back to:
~~~ https://api.buttrbase.com/auth/oidc/conn-apple-uuid-123/callback ~~~
ButtrBase exchanges the authorization code, validates the ID token, and creates or updates the user. The user is then redirected to your redirect_uri with a session_token query parameter.
Complete the login in your app
~~~js // On your /auth/callback page const sessionToken = new URLSearchParams(location.search).get('session_token'); const session = await buttrbase.auth.oidc.handleCallback({ token: sessionToken }); // session.access_token is now available ~~~
Handling Hidden Emails
Apple allows users to hide their real email and use a relay address (xyz@privaterelay.appleid.com). ButtrBase records and uses the relay address as the user's primary email. To send emails to these users you must configure your email domain with Apple's relay service:
1. In Apple Developer → Services → Sign In with Apple for Email Communication.
2. Register your email domain and any From addresses you use for transactional mail.
ButtrBase will pass through the relay address in all API responses. Your application should handle relay emails gracefully — do not block or reject them.
SDK Examples
Python
~~~python url = client.oidc_authorize_url("conn-apple-uuid-123")
redirect user to url["redirect_url"]
On callback:
resp = client.oidc_callback({"code": "auth-code", "state": "state-value"}) ~~~Node.js / TypeScript
~~~typescript const url = await client.oidcAuthorizeUrl('conn-apple-uuid-123'); // redirect user to url.redirect_url
// On callback: const resp = await client.oidcCallback('conn-apple-uuid-123', { code: 'auth-code' }); ~~~
Go
~~~go url, err := client.OidcAuthorizeURL(ctx, "conn-apple-uuid-123") // redirect user
// On callback: resp, err := client.OidcCallback(ctx, "conn-apple-uuid-123", map[string]any{"code": "auth-code"}) ~~~
Rust
~~~rust let auth = client.oidc_authorize_url("conn-apple-uuid-123").await?; // redirect user
// On callback: let mut params = std::collections::HashMap::new(); params.insert("code".into(), "auth-code".into()); let result = client.oidc_callback(¶ms).await?; ~~~
Mobile Apps (iOS / macOS)
For native iOS/macOS apps using the ASAuthorizationController API, use the native SIWA flow to obtain an Apple identityToken (a JWT), then exchange it with ButtrBase:
~~~json POST /api/auth/oidc/apple/native-exchange Content-Type: application/json
{ "identity_token": "eyJ...", "authorization_code": "c12345abc...", "connection_uuid": "conn-apple-uuid-123" } ~~~
Response:
~~~json { "access_token": "eyJ...", "refresh_token": "rt_...", "user": { "id": "user-uuid-123", "email": "user@privaterelay.appleid.com" } } ~~~
Testing
Apple requires a real Apple ID and a physical device or simulator for end-to-end testing. Use the ButtrBase sandbox environment to avoid polluting production user data during development:
~~~ https://api.buttrbase.com/sandbox/auth/oidc/conn-apple-uuid-123/authorize ~~~
Sandbox mode captures the token exchange but does not create real user records.
Known Limitations
- Apple does not provide a phone number or profile picture.
- The user's full name is only sent on the first sign-in. Cache it at first login.
- Apple's OIDC discovery document deviates slightly from the spec; ButtrBase normalizes this automatically.
- SIWA is not available in web apps served from
localhost(Apple enforces HTTPS). Use a tunnel (e.g.ngrok) for local development.
Error Reference
| Status | Code | Meaning |
|--------|------|---------|
| 400 | invalid_connection | OIDC connection UUID not found or not an Apple connection |
| 400 | invalid_state | OAuth state parameter mismatch |
| 400 | missing_code | Authorization code absent from callback |
| 401 | token_exchange_failed | Apple rejected the code or client secret JWT |
| 403 | domain_mismatch | User's email domain not in allowed_domains |
| 502 | provider_error | Apple's identity service returned an error |