Sign in with Apple
Auth
Sign in with Apple
Exchange a Firebase Apple ID token for a Swappr session. Creates the account on first use.
POST
Sign in with Apple
Overview
Signs a user in (or registers them on first use) with Sign in with Apple. The mobile app performs the native Apple authorization, exchanges the resulting Apple identity token for a Firebase credential, and POSTs the Firebase ID token here — the same request body and response shape as/oauth/google.
The backend verifies the token with firebase-admin (signature, expiry, issuer, audience = our Firebase project) and additionally checks that the token’s firebase.sign_in_provider claim is apple.com. That pin matters: both providers mint Firebase ID tokens for the same project, so the signature check alone cannot tell them apart, and without it a Google token POSTed here would create an APPLE-bound account.
There are three outcomes:
- First-time Apple user → a new account is created with
oauthProvider: 'APPLE'. The response is201 CreatedwithisNewUser: true. The client should route the user into onboarding. - Returning Apple user (same Apple identity) →
200 OKwithisNewUser: false. - Email already owned by a non-Apple account →
409 ACCOUNT_EXISTS_VIA_OAUTH. Swappr does not silently attach Apple to a pre-existing email/password (or Google) account.
Private-relay emails. When the user chooses Hide My Email, Apple supplies a
@privaterelay.appleid.com address instead of their real one. It is a real,
deliverable address and is stored and treated exactly like any other — nothing
downstream special-cases it. Note that the relay address is per-app, so the
same person signing in with Google will have a different email and therefore a
separate account.Name is only ever sent once. Apple returns the user’s full name on the first
authorization only, never on subsequent sign-ins. Swappr does not depend on it:
OAuth accounts start with
firstName / lastName as null and collect the name
during onboarding, exactly as Google accounts do.Authentication
None required. The Firebase ID token in the body is the credential.Path parameters
None.Query parameters
None.Request body
| Field | Type | Required | Allowed values | Example |
|---|---|---|---|---|
idToken | string | yes | A Firebase ID token (signed JWT) obtained by exchanging the Apple identity token. 1..8192 chars. | eyJhbGciOiJSUzI1NiIsImtpZCI6... |
Example payload
Response — 201 Created (new user) / 200 OK (returning user)
| Field | Type | Notes | Example |
|---|---|---|---|
accessToken | string | Short-lived RS256 JWT (15 min). Send as Authorization: Bearer <token>. | eyJhbGci... |
refreshToken | string | Opaque 256-bit token. Exchange via /refresh. | r8Kf... |
isNewUser | boolean | true when this call created the account (HTTP 201), false for a returning user (HTTP 200). | true |
user.id | string | The user’s ID. | usr_01HZQ7K3M4N5P6Q7R8S9T0V1W2 |
user.email | string | The Apple email, lowercased. May be a private-relay address. | zx9q7w@privaterelay.appleid.com |
user.firstName | string | null | null until set during onboarding / profile edit. | null |
user.lastName | string | null | null until set. | null |
user.avatarUrl | string | null | null until set. | null |
user.onboardingStep | string | Current onboarding step; first step for a brand-new user. | verify_tenancy |
user.tenancyStatus | string | Tenancy verification status. | not_submitted |
user.subscriptionStatus | string | Subscription state. | free_launch |
Example response — 201
Error responses
| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_FAILED | idToken missing, blank, or longer than 8192 chars. |
| 401 | OAUTH_INVALID_TOKEN | Token failed verification — malformed, expired, wrong signature, minted for a different Firebase project, carried no email, or its sign_in_provider was not apple.com. |
| 403 | ACCOUNT_BANNED | The matched account is banned. |
| 409 | ACCOUNT_EXISTS_VIA_OAUTH | The email already belongs to a non-Apple account. Sign in with the original method, then link Apple. |
| 503 | OAUTH_NOT_CONFIGURED | Firebase credentials (FCM_SERVICE_ACCOUNT_JSON + FIREBASE_PROJECT_ID) are not set in this environment. |
Example error — 401
Side effects
- First-time sign-in only: inserts a new row into the
userscollection withoauthProvider: 'APPLE',oauthIdset to the Firebaseuid,passwordHash: null, andemailVerifiedtaken from the token. - Issues a new session: inserts a
refresh_tokensrow and signs an access JWT. - No email is sent (Apple has already verified the address).
Rate limiting
This endpoint shares the auth burst limiter (10 requests / minute / IP) with/login, on top of the router-level /auth/* cap.
Notes for the client
- The iOS app must have the Sign in with Apple capability on its App ID and the
com.apple.developer.applesigninentitlement. - Generate a random nonce, pass its SHA-256 hash to Apple, and hand the raw nonce to Firebase alongside the identity token. This binds the token to that one sign-in attempt so it cannot be replayed.
- Read the Firebase ID token (
user.getIdToken()), not the Apple identity token, and POST it here. - The button is iOS-only: Apple only requires it on Apple platforms, and the Android path would need a separate Services ID that Swappr does not configure.
See also
- Sign in with Google — the equivalent Google flow.
- Login (email + password) — the password flow.
- Errors — full error code catalog.