Olympus Docs
CookbookSocial login

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

  1. Apple Developer → Identifiers → + App ID. App ID for your service (e.g. com.your-domain.olympus).
  2. Apple Developer → Identifiers → + Services ID. Services ID for the actual OAuth2 client (e.g. com.your-domain.olympus.auth). Enable Sign in with Apple.
  3. 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.
  4. Apple Developer → Keys → + Sign in with Apple key. Configure for your App ID. Download the .p8 private key.
  5. 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.jsonnet

Kratos'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.

On this page