Plugins

Apple

Official plugin — Sign in with Apple with form_post callbacks and an ES256 client-secret JWT.

Sign in with Apple is OIDC with three twists: the web flow posts its response, every exchange needs an ES256 client-secret JWT you sign, and email_verified can arrive as the string "false".

The one rule

Create a Sign in with Apple key (Team ID, Key ID, .p8 private key), seed the key in Vault, and register your exact callback URI — Apple validates all three on every exchange.

Quick start

Create a key

In the Apple Developer portal: Identifiers → register an App ID with Sign In with Apple; Keys → create a key with that capability; note the Team ID and Key ID, and download the .p8 once.

Plug it

src/app.ts
import { oke } from "okengine";
import { oauth } from "okengine/plugins";

export const app = oke({
  name: "shop",
  env: "dev",
  gate: { auth: {} },
}).plug(
  oauth({
    baseUrl: "https://app.example.com",
    providers: {
      apple: {
        enabled: true,
        teamId: "ABCDE12345",
        keyId: "XYZ6789012",
      },
    },
  }),
);

Seed the private key

# .env.local
OAUTH_APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEH...
-----END PRIVATE KEY-----"

The driver mints a fresh ES256 client-secret JWT per exchange (iss = team, kid = key, one-hour life) and discards nothing to disk.

How identity works

AspectBehavior
CallbackApple posts code + state as a form body — both GET and POST are bound
SignatureES256 against appleid.apple.com JWKS
Issuermust equal https://appleid.apple.com
Namedelivered only on first authorization via the form-posted user field
Email truststrict parse — only boolean true, "true", or "1" count

Consequence: the string "false" stays unverified. Naive truthiness would mark every private-relay email verified — that is the takeover bug class this parse exists to close.

Default scopes: name, email.

Options

OptionTypeDefaultMeaning
enabledbooleanfalseTurn the provider on
clientIdstringVault/env*\*OAUTH_APPLE_CLIENT_ID (Services ID)
teamIdstringrequiredApple Developer Team ID
keyIdstringrequiredPrivate-key identifier
redirectUristring{baseUrl}…/appleExact registered URI
scopesstring[]name emailRequested scopes
storeProviderTokensbooleanfalseKeep tokens in Vault

Surfaces

FlowPath
StartPOST /auth/oauth/apple/start
CallbackGET+POST /auth/oauth/callback/apple
LinkPOST /auth/oauth/apple/link

Troubleshooting

Learn more

  • OAuth — shared flows and security model
  • Vault — seeding OAUTH_APPLE_PRIVATE_KEY
  • Gategate.auth

Next

On this page