Add Apple as a social login provider
Configure Sign in with Apple as an alternate login method
Apple's OIDC implementation has quirks. This recipe handles them.
Prerequisites
- An Apple Developer account.
- An Olympus deployment with HTTPS (Apple rejects non-HTTPS callbacks).
Step 1: Apple Developer setup
- Apple Developer → Identifiers → + App ID. App ID for your service (e.g.
com.your-domain.olympus). - Apple Developer → Identifiers → + Services ID. Services ID for the actual OAuth2 client (e.g.
com.your-domain.olympus.auth). Enable Sign in with Apple. - Configure the Services ID:
- Primary App ID: select the one above.
- Domains:
ciam.<your-domain>. - Return URLs:
https://ciam.<your-domain>/self-service/methods/oidc/callback/apple.
- Apple Developer → Keys → + Sign in with Apple key. Configure for your App ID. Download the
.p8private key. - Note: Team ID, Key ID, Services ID (the OAuth client ID).
Step 2: Generate a client secret JWT
Apple requires the client secret to be a signed JWT (not a static string). Generate per-deployment:
# Apple's client secret JWT (valid 6 months max)
HEADER='{"alg":"ES256","kid":"<your-key-id>","typ":"JWT"}'
PAYLOAD='{
"iss": "<your-team-id>",
"iat": '$(date +%s)',
"exp": '$(($(date +%s) + 15777000))',
"aud": "https://appleid.apple.com",
"sub": "<your-services-id>"
}'
# Sign with the .p8 key
# (use jwt-cli or a small script)Refresh this JWT every 6 months. A planned Operate, Apple key rotation runbook would document this; for now, calendar it.
Step 3: Configure Kratos
selfservice:
methods:
oidc:
enabled: true
config:
providers:
- id: apple
provider: apple
client_id: <services-id> # e.g. com.your-domain.olympus.auth
client_secret: <signed-jwt>
apple_team_id: <team-id>
apple_private_key_id: <key-id>
apple_private_key: |
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
scope:
- email
- name
mapper_url: file:///etc/config/kratos/oidc.apple.jsonnetKratos's Apple provider auto-generates the JWT from the private key fields. You may not need to pre-sign the JWT, check the Kratos version's documentation.
Step 4: Mapper
oidc.apple.jsonnet:
local claims = std.extVar('claims');
{
identity: {
traits: {
email: claims.email,
name: {
first: if 'given_name' in claims then claims.given_name else null,
last: if 'family_name' in claims then claims.family_name else null,
},
},
},
}Caveat: Apple only sends name claims on the first login. Subsequent logins return only sub and email. If a user revokes Apple login and re-grants, the name might be re-sent. Plan your trait schema to handle missing names gracefully.
Step 5: Test
Open https://ciam.<your-domain>/login and click Continue with Apple.
Apple's quirk: by default, users can hide their email from your app. In that case, you receive a relay address (abc123@privaterelay.appleid.com). The address still works for delivery but it's not the user's real email.
Decide: do you allow private relay addresses, or require the real email? Set in your app's policy and reject relay addresses if necessary.