Skip to content

Sign in with Apple

MAIL-181 / MAIL-183 / MAIL-184: Duckbill™ authenticates the application user with Sign in with Apple. This is not Microsoft Graph or Gmail OAuth. See ADR 0028. The signed ia_session cookie from application authentication is unchanged.

Product path

  1. Open /login and choose Sign in with Apple, or start at /api/auth/apple/start.
  2. Apple authenticates the person and form_posts code, id_token, and state to the environment callback URL.
  3. Duckbill verifies the Apple id_token, upserts inbox_agent.users on Apple sub, and sets HTTP-only ia_session.
  4. The browser continues to an allowlisted path (/, /connect, /docs, or /accounts) so the user can authorize a mailbox. Provider tokens stay on the server.

Unauthenticated API calls still return UNAUTHENTICATED.

Callback URLs

Register these Return URLs on the Apple Services ID. They must match APPLE_REDIRECT_URI when that variable is set.

EnvironmentReturn URL
Localhttp://localhost:3000/api/auth/apple/callback
Previewhttps://duckbill-dev.jasonkoch.io/api/auth/apple/callback
Productionhttps://duckbill.jasonkoch.io/api/auth/apple/callback

Also register the hostnames localhost, duckbill-dev.jasonkoch.io, and duckbill.jasonkoch.io as Apple Website URLs / domains.

Apple Developer registration (MAIL-183)

Do this in an Apple Developer Program account. Do not put key material in git, Jira, screenshots, or chat.

  1. Create or reuse an App ID with the Sign in with Apple capability.
  2. Create a Services ID (this value is APPLE_CLIENT_ID). Enable Sign in with Apple and configure the domains and Return URLs above.
  3. Create a Sign in with Apple key. Note APPLE_KEY_ID. Download the .p8 once and store it in the host secret store as APPLE_PRIVATE_KEY. Never commit the file.
  4. Note the Team ID as APPLE_TEAM_ID.

Host environment

Names only. Values stay in the Vercel / local secret store.

VariablePurpose
APPLE_CLIENT_IDApple Services ID
APPLE_TEAM_IDApple Developer Team ID
APPLE_KEY_IDSign in with Apple key id
APPLE_PRIVATE_KEYPKCS#8 contents of the .p8 (PEM or raw body)
APPLE_REDIRECT_URIOptional override; defaults from APP_ENV to the table above
SESSION_SECRETExisting 32+ character signer for ia_session
DATABASE_URLUsers and apple_auth_states
APP_ENVlocal, preview, or production (Secure cookies on preview/production)

loadRequiredEnv does not require the Apple variables. The Apple routes return 503 until they are set so existing hosts keep booting.

Owner session (OWNER_SESSION_ENABLED, OWNER_IDENTITY_SUBJECT) remains an ops escape. Do not put provider tokens in that cookie either.

Token verification

Apple's id_token is RS256. Keys come from https://appleid.apple.com/auth/keys and are RSA JWKs (kty, n, e, kid). The .p8 is only for the ES256 client secret used at /auth/token. Do not import Apple's JWKS as EC (x / y); that fails with key.n undefined.

Session claims

After Apple succeeds, claims are only:

v, userId, iat, exp, csrf

Apple id_token, Apple authorization code, Apple client secret, Microsoft Graph tokens, and Gmail tokens are discarded. They are never session fields.

Migration

0020_apple_identity.sql documents the Apple sub → identity_subject mapping and adds inbox_agent.apple_auth_states for OIDC state / nonce.