diff --git a/docs/06-concepts/11-authentication/04-providers/04-apple/01-setup.md b/docs/06-concepts/11-authentication/04-providers/04-apple/01-setup.md index 93ee3be2..ca657c6a 100644 --- a/docs/06-concepts/11-authentication/04-providers/04-apple/01-setup.md +++ b/docs/06-concepts/11-authentication/04-providers/04-apple/01-setup.md @@ -2,29 +2,91 @@ Sign-in with Apple requires that you have a subscription to the [Apple Developer Program](https://developer.apple.com/programs/), even if you only want to test the feature in development mode. -:::note -Right now, we have official support for iOS, macOS, Android, and Web for Sign in with Apple. -::: :::caution You need to install the auth module before you continue, see [Setup](../../setup). ::: -## Create your credentials +## Get your credentials + +All platforms require an App ID. Android and Web additionally require a Service ID. + +| Platform | App ID | Service ID | Xcode capability | Android intent filter | +| --- | --- | --- | --- | --- | +| iOS / macOS | Required | Not needed | Required | — | +| Android | Required | Required | — | Required | +| Web | Required | Required | — | — | + +### Register your App ID + +1. In [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list), click **Identifiers → +**. + +2. Select **App IDs** and click **Continue**. + + ![Register a new identifier — App IDs selected](/img/authentication/providers/apple/4-app-id-create.png) + +3. Select **App** as the type and click **Continue**. + +4. Fill in a description and your app's **Bundle ID** (e.g. `com.example.app`). + +5. Scroll down to **Capabilities**, find **Sign in with Apple**, and check it. Keep it set as a **primary App ID**. + + ![App ID capabilities — Sign in with Apple enabled](/img/authentication/providers/apple/5-app-id-capability.png) + +6. Click **Continue**, then **Register**. + +### Create a Service ID (Android and Web only) + +Skip this section if you are building for iOS or macOS only. + +1. In Certificates, Identifiers & Profiles, click **Identifiers → +**. + +2. Select **Services IDs** and click **Continue**. + + ![Register a new identifier — Services IDs selected](/img/authentication/providers/apple/6-service-id-create.png) + +3. Enter a description and a unique **Identifier** (e.g. `com.example.service`). This value becomes your `serviceIdentifier`. Click **Continue**, then **Register**. + +4. Click on the Service ID you just created. Check **Sign in with Apple** and click **Configure**. + +5. In the modal, set: + - **Primary App ID**: the App ID from the previous section + - **Domains and Subdomains**: your domain (e.g. `example.com`) + - **Return URLs**: your server's callback route (e.g. `https://example.com/auth/callback`) + + ![Web Authentication Configuration — Primary App ID, domains, and return URLs](/img/authentication/providers/apple/7-service-id-configure.png) + +6. Click **Next**, then **Done**, then **Save**. + +:::warning +All return URLs must use **HTTPS**. Apple rejects HTTP redirect URIs. For local development, expose your server over HTTPS using a tunnelling service. +::: + +### Create a Sign in with Apple key + +1. In Certificates, Identifiers & Profiles, click **Keys → +**. + +2. Enter a key name, check **Sign in with Apple**, and click **Configure**. Select your primary App ID and click **Save**. + + ![Configure key — Sign in with Apple checked, primary App ID selected](/img/authentication/providers/apple/8-key-create.png) + +3. Click **Continue**, then **Register**. -1. **Create a Service ID** in the [Apple Developer Portal](https://developer.apple.com/account/resources/identifiers/list/serviceId): - - Register a new Service ID - - Enable "Sign in with Apple" - - Configure domains and redirect URLs - - Note the Service Identifier (e.g., `com.example.service`) + ![Register a New Key — review screen](/img/authentication/providers/apple/8-key-register.png) -2. **Create a Key** for Sign in with Apple: - - Go to Keys section in Apple Developer Portal - - Create a new key with "Sign in with Apple" enabled - - Download the key file (`.p8` file) - **you can only download this once** - - Note the Key ID +4. Download the `.p8` key file immediately — **you can only download it once**. Note the **Key ID** shown on this page. -3. **Store the credentials securely** in your `config/passwords.yaml`: + ![Download Your Key — one-time download warning with Key ID visible](/img/authentication/providers/apple/8-key-download.png) + +5. Find your **Team ID** in your [Apple Developer Account](https://developer.apple.com/account) under Membership. + +:::note +Each primary App ID can have a maximum of two private keys. If you reach the limit, revoke an existing key before creating a new one. See [Create a Sign in with Apple private key](https://developer.apple.com/help/account/configure-app-capabilities/create-a-sign-in-with-apple-private-key/) for details. +::: + +### Store your credentials + +Add the credentials to `config/passwords.yaml`: ```yaml development: @@ -44,8 +106,11 @@ development: ``` :::warning -The Apple private key can only be downloaded once. Store it securely and never commit it to version control. Use environment variables or secure secret management in production. -::: +**Never commit your `.p8` key to version control.** Use environment variables or a secrets manager in production. + +**Paste the raw `.p8` key contents** — the full text including `-----BEGIN PRIVATE KEY-----` and `-----END PRIVATE KEY-----`. Do not pre-generate a JWT from it. Serverpod generates the client secret JWT internally on every request. + +**Carefully maintain correct indentation for YAML block scalars.** The `appleKey` block uses a `|`; any indentation error will silently break the key, resulting in authentication failures without helpful error messages. ## Server-side configuration @@ -99,25 +164,26 @@ void run(List args) async { :::tip You can use the `AppleIdpConfigFromPasswords` constructor in replacement of the `AppleIdpConfig` above to automatically load the credentials from the `config/passwords.yaml` file or environment variables. It will expect either the following keys on the file: - - `appleServiceIdentifier` - - `appleBundleIdentifier` - - `appleRedirectUri` - - `appleTeamId` - - `appleKeyId` - - `appleKey` - - `appleWebRedirectUri` (optional, for Web support when using server callback route) - - `appleAndroidPackageIdentifier` (optional, for Android support) +- `appleServiceIdentifier` +- `appleBundleIdentifier` +- `appleRedirectUri` +- `appleTeamId` +- `appleKeyId` +- `appleKey` +- `appleWebRedirectUri` (optional, for Web support when using server callback route) +- `appleAndroidPackageIdentifier` (optional, for Android support) Or the following environment variables: - - `SERVERPOD_PASSWORD_appleServiceIdentifier` - - `SERVERPOD_PASSWORD_appleBundleIdentifier` - - `SERVERPOD_PASSWORD_appleRedirectUri` - - `SERVERPOD_PASSWORD_appleTeamId` - - `SERVERPOD_PASSWORD_appleKeyId` - - `SERVERPOD_PASSWORD_appleKey` - - `SERVERPOD_PASSWORD_appleWebRedirectUri` (optional, for Web support when using server callback route) - - `SERVERPOD_PASSWORD_appleAndroidPackageIdentifier` (optional, for Android support) +- `SERVERPOD_PASSWORD_appleServiceIdentifier` +- `SERVERPOD_PASSWORD_appleBundleIdentifier` +- `SERVERPOD_PASSWORD_appleRedirectUri` +- `SERVERPOD_PASSWORD_appleTeamId` +- `SERVERPOD_PASSWORD_appleKeyId` +- `SERVERPOD_PASSWORD_appleKey` +- `SERVERPOD_PASSWORD_appleWebRedirectUri` (optional, for Web support when using server callback route) +- `SERVERPOD_PASSWORD_appleAndroidPackageIdentifier` (optional, for Android support) + ::: Then, extend the abstract endpoint to expose it on the server: @@ -128,7 +194,16 @@ import 'package:serverpod_auth_idp_server/providers/apple.dart'; class AppleIdpEndpoint extends AppleIdpBaseEndpoint {} ``` -Finally, run `serverpod generate` to generate the client code and create a migration to initialize the database for the provider. More detailed instructions can be found in the general [identity providers setup section](../../setup#identity-providers-configuration). +Run `serverpod generate` to generate the client code, then create and apply a database migration to initialize the provider's tables: + +```bash +serverpod generate +dart run bin/main.dart --apply-migrations +``` + +:::note +Skipping the migration will cause the server to crash at runtime when the Apple provider tries to read or write user data. More detailed instructions can be found in the general [identity providers setup section](../../setup#identity-providers-configuration). +::: ### Basic configuration options @@ -150,7 +225,9 @@ For more details on configuration options, see the [configuration section](./con The `serverpod_auth_idp_flutter` package implements the sign-in logic using [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple). The documentation for this package should in most cases also apply to the Serverpod integration. -_Note that Sign in with Apple may not work on some versions of the Simulator (iOS 13.5 works). This issue doesn't affect real devices._ +:::note +Sign in with Apple may not work correctly on all Simulator versions. If you run into issues during development, test on a physical device to confirm whether the problem is Simulator-specific. +::: ### iOS and macOS @@ -174,8 +251,24 @@ To enable this: 1. Add the `androidPackageIdentifier` to your `AppleIdpConfig` (or the `appleAndroidPackageIdentifier` key in `passwords.yaml`). This must match your app's Android package name (e.g., `com.example.app`). 2. Configure the redirect URI in your Apple Developer Portal to point to your server's callback route (e.g., `https://example.com/auth/callback`). +3. Register the `signinwithapple` URI scheme in your `AndroidManifest.xml`: + +```xml + + + + + + + + +``` -No additional client-side Android configuration is needed beyond what the [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) package requires. +:::warning +This intent filter is required. Without it, the OAuth callback never returns to your app and sign-in silently hangs. +::: ### Web @@ -188,6 +281,10 @@ To enable this: If `webRedirectUri` is not configured, Web callbacks to the server route will fail. +:::warning +All redirect URIs must use **HTTPS**. Apple rejects HTTP URLs, including `localhost`. For local development, expose your server over HTTPS using a tunnelling service, like ngrok or Cloudflare Tunnel. +::: + ## Present the authentication UI ### Initializing the `AppleSignInService` @@ -233,9 +330,23 @@ AppleSignInWidget( ) ``` +This renders an Apple sign-in button like this: + +![Apple sign-in button](/img/authentication/providers/apple/3-button.png) + The widget automatically handles: + - Apple Sign-In flow for iOS, macOS, Android, and Web. - Token management. - Underlying Apple Sign-In package error handling. For details on how to customize the Apple Sign-In UI in your Flutter app, see the [customizing the UI section](./customizing-the-ui). + +:::warning +**Apple sends the user's email address and full name only on the first sign-in.** On all subsequent sign-ins, neither is included in the response. If your server does not persist them during that first authentication, they cannot be retrieved later. + +Use the `sub` claim (the stable user identifier) to identify users. Do not use the email address, as it may change when a user updates their "Hide My Email" settings. For more information, see [Authenticating users with Sign in with Apple](https://developer.apple.com/documentation/sign_in_with_apple/authenticating-users-with-sign-in-with-apple). + +--- + +If you run into issues, see the [troubleshooting guide](./troubleshooting). diff --git a/docs/06-concepts/11-authentication/04-providers/04-apple/02-configuration.md b/docs/06-concepts/11-authentication/04-providers/04-apple/02-configuration.md index 3672c7d1..7ab368c8 100644 --- a/docs/06-concepts/11-authentication/04-providers/04-apple/02-configuration.md +++ b/docs/06-concepts/11-authentication/04-providers/04-apple/02-configuration.md @@ -41,6 +41,7 @@ Apple Sign-In requires web routes for handling callbacks and notifications. Thes The `revokedNotificationRoutePath` is the path that Apple will call when a user revokes their authorization. The `webAuthenticationCallbackRoutePath` is the path that Apple will call when a user completes the sign-in process. These routes are configured in the `pod.configureAppleIdpRoutes()` method: + ```dart pod.configureAppleIdpRoutes( revokedNotificationRoutePath: '/hooks/apple-notification', @@ -48,11 +49,18 @@ pod.configureAppleIdpRoutes( ); ``` -### Configuring Apple Sign-In on the App +- `revokedNotificationRoutePath` (default: `'/hooks/apple-notification'`): The path Apple calls when a user revokes authorization. Register this URL in your Apple Developer Portal for server-to-server notifications. +- `webAuthenticationCallbackRoutePath` (default: `'/auth/callback'`): The path Apple redirects to after the user completes web-based sign-in. Must match the return URL registered on your Service ID. + +:::note +When a user revokes access from their Apple ID settings, Apple sends a notification to `revokedNotificationRoutePath`. Serverpod receives this notification automatically. You are responsible for invalidating any active sessions for that user in your own application logic. +::: + +## Configuring Apple Sign-In on the app -Apple Sign-In requires additional configuration for web and Android platforms. On native Apple platforms (iOS/macOS), the configuration is automatically handled by the underlying `sign_in_with_apple` package through Xcode capabilities. +Apple Sign-In requires additional configuration for web and Android platforms. On native Apple platforms (iOS/macOS), the configuration is handled automatically by the underlying `sign_in_with_apple` package through Xcode capabilities. -#### Passing Configuration in Code +### Passing configuration in code You can pass the configuration directly when initializing the Apple Sign-In service: @@ -65,18 +73,23 @@ client.auth.initializeAppleSignIn( The `serviceIdentifier` is your Apple Services ID (configured in Apple Developer Portal), and the `redirectUri` is the callback URL that Apple will redirect to after authentication (must match the URL configured on the server). +Both parameters are optional. If not supplied, the provider falls back to the corresponding `--dart-define` build variable: + +- `serviceIdentifier` → `APPLE_SERVICE_IDENTIFIER` +- `redirectUri` → `APPLE_REDIRECT_URI` + :::note -These parameters are only required for web and Android platforms. On native Apple platforms (iOS/macOS), they are ignored and the configuration from Xcode capabilities is used instead. +These parameters are only required for web and Android platforms. On native Apple platforms (iOS/macOS), they are ignored, and the configuration from Xcode capabilities is used instead. ::: -#### Using Environment Variables +### Using Environment Variables -Alternatively, you can pass configuration during build time using the `--dart-define` option. The Apple Sign-In provider supports the following environment variables: +Alternatively, you can pass configuration during build time using the `--dart-define` option: - `APPLE_SERVICE_IDENTIFIER`: The Apple Services ID. - `APPLE_REDIRECT_URI`: The redirect URI for authentication callbacks. -If `serviceIdentifier` and `redirectUri` values are not supplied when initializing the service, the provider will automatically fetch them from these environment variables. +If you do not supply `serviceIdentifier` and `redirectUri` values when initializing the service, the provider will automatically fetch them from these environment variables. **Example usage:** @@ -91,8 +104,34 @@ This approach is useful when you need to: - Manage configuration separately for different platforms (Android, Web) in a centralized way. - Avoid committing sensitive configuration to version control. -- Configure different credentials for different build environments (development, staging, production). +- Configure different credentials for different build environments, like development, staging, and production. :::tip You can also set these environment variables in your IDE's run configuration or CI/CD pipeline to avoid passing them manually each time. ::: + +## `AppleIdpConfig` parameter reference + +| Parameter | Type | Required | `passwords.yaml` key | Description | +| --- | --- | --- | --- | --- | +| `serviceIdentifier` | `String` | Yes (Android/Web) | `appleServiceIdentifier` | The Services ID identifier (e.g. `com.example.service`). Used as the OAuth client ID for Android and Web. Not required for iOS/macOS-only setups. | +| `bundleIdentifier` | `String` | Yes | `appleBundleIdentifier` | The App ID bundle identifier (e.g. `com.example.app`). Used as the client ID for native Apple platform sign-in. | +| `redirectUri` | `String` | Yes (Android/Web) | `appleRedirectUri` | The server callback route Apple redirects to after sign-in (e.g. `https://example.com/auth/callback`). Must be HTTPS and match the return URL registered on your Service ID. | +| `teamId` | `String` | Yes | `appleTeamId` | The 10-character Team ID from your Apple Developer account (e.g. `ABC123DEF4`). Used to sign the client secret JWT. | +| `keyId` | `String` | Yes | `appleKeyId` | The Key ID of the Sign in with Apple private key (e.g. `XYZ789ABC0`). | +| `key` | `String` | Yes | `appleKey` | The raw contents of the `.p8` private key file, including the `-----BEGIN PRIVATE KEY-----` header and footer. Serverpod uses this to generate a short-lived client secret JWT on each request. Do not pre-generate the JWT yourself. | +| `webRedirectUri` | `String?` | Web only | `appleWebRedirectUri` | The web app URL that the browser is redirected to after the server receives Apple's callback. This is required when using the server callback route for Web. | +| `androidPackageIdentifier` | `String?` | Android only | `appleAndroidPackageIdentifier` | The Android package name (e.g. `com.example.app`). When set, the callback route redirects Android clients back to the app via an intent URI using the `signinwithapple` scheme. | + +### Environment Variable equivalents + +All `passwords.yaml` keys can be set as environment variables by prefixing with `SERVERPOD_PASSWORD_`: + +- `appleServiceIdentifier` → `SERVERPOD_PASSWORD_appleServiceIdentifier` +- `appleBundleIdentifier` → `SERVERPOD_PASSWORD_appleBundleIdentifier` +- `appleRedirectUri` → `SERVERPOD_PASSWORD_appleRedirectUri` +- `appleTeamId` → `SERVERPOD_PASSWORD_appleTeamId` +- `appleKeyId` → `SERVERPOD_PASSWORD_appleKeyId` +- `appleKey` → `SERVERPOD_PASSWORD_appleKey` +- `appleWebRedirectUri` → `SERVERPOD_PASSWORD_appleWebRedirectUri` +- `appleAndroidPackageIdentifier` → `SERVERPOD_PASSWORD_appleAndroidPackageIdentifier` diff --git a/docs/06-concepts/11-authentication/04-providers/04-apple/04-troubleshooting.md b/docs/06-concepts/11-authentication/04-providers/04-apple/04-troubleshooting.md new file mode 100644 index 00000000..535ef46f --- /dev/null +++ b/docs/06-concepts/11-authentication/04-providers/04-apple/04-troubleshooting.md @@ -0,0 +1,129 @@ +# Troubleshooting + +This page helps you identify common Sign in with Apple failures, explains why they occur, and shows how to resolve them. For Apple's full list of OAuth error codes, see [TN3107: Resolving Sign in with Apple response errors](https://developer.apple.com/documentation/technotes/tn3107-resolving-sign-in-with-apple-response-errors). + +## Setup checklist + +Go through this before investigating a specific error. Most problems come from a missed step. + +* [ ] Enable **Sign in with Apple** on your App ID at [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list). +* [ ] Add **Sign in with Apple** under Signing & Capabilities in Xcode (*iOS/macOS only*). +* [ ] Create a **Service ID** and link it to your App ID (*Android and Web only*). +* [ ] Confirm the **return URL** on the Service ID uses `https://` (not `http://` or `localhost`). +* [ ] Make sure **`appleKey`** in your config holds the raw `.p8` file contents (not a pre-generated JWT). +* [ ] Double-check the **`.p8` key** is indented consistently under `appleKey: |` in `passwords.yaml`. +* [ ] Run **`serverpod generate`** after adding the Apple provider, and apply migrations using `--apply-migrations`. +* [ ] Call **`pod.configureAppleIdpRoutes(...)`** on the server before the pod starts. +* [ ] Add the **`signinwithapple`** intent filter to `AndroidManifest.xml` (*Android only*). +* [ ] Add **Apple's mail servers** to your SPF record if you email users who might use Hide My Email. + +## Sign-in fails with `invalid_client` every time + +**Problem:** Every authentication attempt gives an `invalid_client` error from Apple. + +**Cause:** The `appleKey` value in `passwords.yaml` is not indented correctly. The key gets corrupted during parsing. The server starts without error, but Apple sees an invalid signature. + +**Resolution:** Paste the raw `.p8` key under `appleKey` with consistent indentation. All lines of the key must line up with the one that starts `-----BEGIN PRIVATE KEY-----`. For example: + +```yaml +appleKey: | + -----BEGIN PRIVATE KEY----- + MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg... + -----END PRIVATE KEY----- +``` + +Alternatively, set `appleKey` as an environment variable to avoid YAML indentation entirely. See [Environment Variable equivalents](./configuration#environment-variable-equivalents) in the configuration page. + +## Sign-in starts failing with `invalid_client` after months of success + +**Problem:** Sign-in was working for months, then suddenly fails with `invalid_client` and you haven't changed code. + +**Cause:** `appleKey` has a pre-generated client secret JWT, not the raw `.p8` key. Apple makes JWTs expire after six months. When it expires, all sign-ins fail. + +**Resolution:** Replace any JWT in `appleKey` with the raw `.p8` private key (include the full header and footer). Serverpod will create fresh short-lived JWTs automatically. No need to handle JWTs yourself. See [Creating a client secret](https://developer.apple.com/documentation/accountorganizationaldatasharing/creating-a-client-secret). + +## Sign-in hangs on Android + +**Problem:** The OAuth flow opens a browser, but never returns to the app. Sign-in seems to finish but the app doesn't get the callback. + +**Cause:** The `signinwithapple` URI scheme isn't registered in `AndroidManifest.xml`, so Android drops the callback. + +**Resolution:** Add this activity to `AndroidManifest.xml`: + +```xml + + + + + + + + +``` + +## Server crashes on first Apple sign-in with "no such table" + +**Problem:** The server builds and starts, but crashes when a user tries Apple sign-in. The error cites a missing table (like `serverpod_auth_idp_apple_account`). + +**Cause:** `serverpod generate` has been run, but you didn't create or apply the accompanying database migration. + +**Resolution:** Create and apply the migration: + +```bash +serverpod generate +dart run bin/main.dart --apply-migrations +``` + +## Apple rejects the redirect URI with `invalid_request` + +**Problem:** The web OAuth flow fails with `invalid_request` and Apple's error page says the redirect URI is invalid. + +**Cause:** You're using HTTP instead of HTTPS for the redirect. Apple requires HTTPS and does not allow `localhost`. + +**Resolution:** Always use an HTTPS URL for your redirect. For local development, run your server behind an HTTPS tunnel. Register the tunneled `https://` URL as your return URL in Apple's Developer Portal, and update `appleRedirectUri` in `passwords.yaml` to match. + +## Emails aren't delivered to some users + +**Problem:** Transactional emails (password resets, notifications) work for most people, but some never receive them. + +**Cause:** Some users chose Apple's "Hide My Email" during sign-in. Mail to relay addresses like `@privaterelay.appleid.com` fails if your domain's SPF record doesn't include Apple's mail servers. + +**Resolution:** Add Apple's mail servers to your SPF record. See [Configure private email relay service](https://developer.apple.com/help/account/configure-app-capabilities/configure-private-email-relay-service/) for SPF settings and instructions. + +## User email is `null` after sign-in + +**Problem:** The user's email is missing or `null` after sign in, or it's present on first sign-in but missing after that. + +**Cause:** Apple sends the email address and name only once, during initial authorization. After that, only the `sub` claim is provided. If you didn't save the email the first time, you can't get it again unless the user disconnects and reconnects your app. + +**Resolution:** Make sure your server stores the user's email on their first sign-in. Use `sub` as the main user identifier, not email (which can change if the user updates Hide My Email). See [Authenticating users with Sign in with Apple](https://developer.apple.com/documentation/sign_in_with_apple/authenticating-users-with-sign-in-with-apple). + +## iOS sign-in prompt doesn't show + +**Problem:** Tapping the Sign in with Apple button on iOS has no effect, or the Apple authentication UI never appears. + +**Cause:** The App ID isn't set up with the Sign in with Apple capability, or your Xcode project isn't using that App ID. + +**Resolution:** + +1. In [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list), find your App ID. Check that **Sign in with Apple** is enabled under Capabilities. +2. In Xcode, select your target, open **Signing & Capabilities**, and check that **Sign in with Apple** is listed. If not, click **+ Capability** to add it. +3. Download and install your new provisioning profile if needed. + +## Sign-in does not work in the iOS Simulator + +**Problem:** Sign in with Apple silently fails or the native authentication sheet does not appear when running in the iOS Simulator, but works fine on a physical device. + +**Cause:** Some Simulator versions do not fully support the native Sign in with Apple flow. This is a known Simulator limitation, not a code or configuration issue. + +**Resolution:** Test on a physical device to confirm the problem is Simulator-specific. If sign-in works on a real device, no changes are needed. + +## User stays signed in after removing Apple access + +**Problem:** A user removes your app from Apple ID settings (`Settings > [your name] > Sign-In & Security > Sign in with Apple > Stop Using Apple ID`) but is still logged in to your app. + +**Cause:** Your server receives Apple's revocation notification but doesn't terminate the user's active sessions. + +**Resolution:** When you receive a revocation notification at the route set using `pod.configureAppleIdpRoutes(revokedNotificationRoutePath: ...)`, look up the user by the `sub` value in the payload and invalidate all their sessions. See [Processing changes for Sign in with Apple accounts](https://developer.apple.com/documentation/signinwithapple/processing-changes-for-sign-in-with-apple-accounts) for how the notification works. diff --git a/static/img/authentication/providers/apple/4-app-id-create.png b/static/img/authentication/providers/apple/4-app-id-create.png new file mode 100644 index 00000000..188526c2 Binary files /dev/null and b/static/img/authentication/providers/apple/4-app-id-create.png differ diff --git a/static/img/authentication/providers/apple/5-app-id-capability.png b/static/img/authentication/providers/apple/5-app-id-capability.png new file mode 100644 index 00000000..fe541132 Binary files /dev/null and b/static/img/authentication/providers/apple/5-app-id-capability.png differ diff --git a/static/img/authentication/providers/apple/6-service-id-create.png b/static/img/authentication/providers/apple/6-service-id-create.png new file mode 100644 index 00000000..60a51d75 Binary files /dev/null and b/static/img/authentication/providers/apple/6-service-id-create.png differ diff --git a/static/img/authentication/providers/apple/6-service-id-register.png b/static/img/authentication/providers/apple/6-service-id-register.png new file mode 100644 index 00000000..9b543769 Binary files /dev/null and b/static/img/authentication/providers/apple/6-service-id-register.png differ diff --git a/static/img/authentication/providers/apple/7-service-id-configure.png b/static/img/authentication/providers/apple/7-service-id-configure.png new file mode 100644 index 00000000..d1e70548 Binary files /dev/null and b/static/img/authentication/providers/apple/7-service-id-configure.png differ diff --git a/static/img/authentication/providers/apple/8-key-create.png b/static/img/authentication/providers/apple/8-key-create.png new file mode 100644 index 00000000..419a809a Binary files /dev/null and b/static/img/authentication/providers/apple/8-key-create.png differ diff --git a/static/img/authentication/providers/apple/8-key-download.png b/static/img/authentication/providers/apple/8-key-download.png new file mode 100644 index 00000000..194ccda3 Binary files /dev/null and b/static/img/authentication/providers/apple/8-key-download.png differ diff --git a/static/img/authentication/providers/apple/8-key-register.png b/static/img/authentication/providers/apple/8-key-register.png new file mode 100644 index 00000000..c58e0367 Binary files /dev/null and b/static/img/authentication/providers/apple/8-key-register.png differ diff --git a/versioned_docs/version-3.4.0/06-concepts/11-authentication/04-providers/04-apple/01-setup.md b/versioned_docs/version-3.4.0/06-concepts/11-authentication/04-providers/04-apple/01-setup.md index 93ee3be2..ca657c6a 100644 --- a/versioned_docs/version-3.4.0/06-concepts/11-authentication/04-providers/04-apple/01-setup.md +++ b/versioned_docs/version-3.4.0/06-concepts/11-authentication/04-providers/04-apple/01-setup.md @@ -2,29 +2,91 @@ Sign-in with Apple requires that you have a subscription to the [Apple Developer Program](https://developer.apple.com/programs/), even if you only want to test the feature in development mode. -:::note -Right now, we have official support for iOS, macOS, Android, and Web for Sign in with Apple. -::: :::caution You need to install the auth module before you continue, see [Setup](../../setup). ::: -## Create your credentials +## Get your credentials + +All platforms require an App ID. Android and Web additionally require a Service ID. + +| Platform | App ID | Service ID | Xcode capability | Android intent filter | +| --- | --- | --- | --- | --- | +| iOS / macOS | Required | Not needed | Required | — | +| Android | Required | Required | — | Required | +| Web | Required | Required | — | — | + +### Register your App ID + +1. In [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list), click **Identifiers → +**. + +2. Select **App IDs** and click **Continue**. + + ![Register a new identifier — App IDs selected](/img/authentication/providers/apple/4-app-id-create.png) + +3. Select **App** as the type and click **Continue**. + +4. Fill in a description and your app's **Bundle ID** (e.g. `com.example.app`). + +5. Scroll down to **Capabilities**, find **Sign in with Apple**, and check it. Keep it set as a **primary App ID**. + + ![App ID capabilities — Sign in with Apple enabled](/img/authentication/providers/apple/5-app-id-capability.png) + +6. Click **Continue**, then **Register**. + +### Create a Service ID (Android and Web only) + +Skip this section if you are building for iOS or macOS only. + +1. In Certificates, Identifiers & Profiles, click **Identifiers → +**. + +2. Select **Services IDs** and click **Continue**. + + ![Register a new identifier — Services IDs selected](/img/authentication/providers/apple/6-service-id-create.png) + +3. Enter a description and a unique **Identifier** (e.g. `com.example.service`). This value becomes your `serviceIdentifier`. Click **Continue**, then **Register**. + +4. Click on the Service ID you just created. Check **Sign in with Apple** and click **Configure**. + +5. In the modal, set: + - **Primary App ID**: the App ID from the previous section + - **Domains and Subdomains**: your domain (e.g. `example.com`) + - **Return URLs**: your server's callback route (e.g. `https://example.com/auth/callback`) + + ![Web Authentication Configuration — Primary App ID, domains, and return URLs](/img/authentication/providers/apple/7-service-id-configure.png) + +6. Click **Next**, then **Done**, then **Save**. + +:::warning +All return URLs must use **HTTPS**. Apple rejects HTTP redirect URIs. For local development, expose your server over HTTPS using a tunnelling service. +::: + +### Create a Sign in with Apple key + +1. In Certificates, Identifiers & Profiles, click **Keys → +**. + +2. Enter a key name, check **Sign in with Apple**, and click **Configure**. Select your primary App ID and click **Save**. + + ![Configure key — Sign in with Apple checked, primary App ID selected](/img/authentication/providers/apple/8-key-create.png) + +3. Click **Continue**, then **Register**. -1. **Create a Service ID** in the [Apple Developer Portal](https://developer.apple.com/account/resources/identifiers/list/serviceId): - - Register a new Service ID - - Enable "Sign in with Apple" - - Configure domains and redirect URLs - - Note the Service Identifier (e.g., `com.example.service`) + ![Register a New Key — review screen](/img/authentication/providers/apple/8-key-register.png) -2. **Create a Key** for Sign in with Apple: - - Go to Keys section in Apple Developer Portal - - Create a new key with "Sign in with Apple" enabled - - Download the key file (`.p8` file) - **you can only download this once** - - Note the Key ID +4. Download the `.p8` key file immediately — **you can only download it once**. Note the **Key ID** shown on this page. -3. **Store the credentials securely** in your `config/passwords.yaml`: + ![Download Your Key — one-time download warning with Key ID visible](/img/authentication/providers/apple/8-key-download.png) + +5. Find your **Team ID** in your [Apple Developer Account](https://developer.apple.com/account) under Membership. + +:::note +Each primary App ID can have a maximum of two private keys. If you reach the limit, revoke an existing key before creating a new one. See [Create a Sign in with Apple private key](https://developer.apple.com/help/account/configure-app-capabilities/create-a-sign-in-with-apple-private-key/) for details. +::: + +### Store your credentials + +Add the credentials to `config/passwords.yaml`: ```yaml development: @@ -44,8 +106,11 @@ development: ``` :::warning -The Apple private key can only be downloaded once. Store it securely and never commit it to version control. Use environment variables or secure secret management in production. -::: +**Never commit your `.p8` key to version control.** Use environment variables or a secrets manager in production. + +**Paste the raw `.p8` key contents** — the full text including `-----BEGIN PRIVATE KEY-----` and `-----END PRIVATE KEY-----`. Do not pre-generate a JWT from it. Serverpod generates the client secret JWT internally on every request. + +**Carefully maintain correct indentation for YAML block scalars.** The `appleKey` block uses a `|`; any indentation error will silently break the key, resulting in authentication failures without helpful error messages. ## Server-side configuration @@ -99,25 +164,26 @@ void run(List args) async { :::tip You can use the `AppleIdpConfigFromPasswords` constructor in replacement of the `AppleIdpConfig` above to automatically load the credentials from the `config/passwords.yaml` file or environment variables. It will expect either the following keys on the file: - - `appleServiceIdentifier` - - `appleBundleIdentifier` - - `appleRedirectUri` - - `appleTeamId` - - `appleKeyId` - - `appleKey` - - `appleWebRedirectUri` (optional, for Web support when using server callback route) - - `appleAndroidPackageIdentifier` (optional, for Android support) +- `appleServiceIdentifier` +- `appleBundleIdentifier` +- `appleRedirectUri` +- `appleTeamId` +- `appleKeyId` +- `appleKey` +- `appleWebRedirectUri` (optional, for Web support when using server callback route) +- `appleAndroidPackageIdentifier` (optional, for Android support) Or the following environment variables: - - `SERVERPOD_PASSWORD_appleServiceIdentifier` - - `SERVERPOD_PASSWORD_appleBundleIdentifier` - - `SERVERPOD_PASSWORD_appleRedirectUri` - - `SERVERPOD_PASSWORD_appleTeamId` - - `SERVERPOD_PASSWORD_appleKeyId` - - `SERVERPOD_PASSWORD_appleKey` - - `SERVERPOD_PASSWORD_appleWebRedirectUri` (optional, for Web support when using server callback route) - - `SERVERPOD_PASSWORD_appleAndroidPackageIdentifier` (optional, for Android support) +- `SERVERPOD_PASSWORD_appleServiceIdentifier` +- `SERVERPOD_PASSWORD_appleBundleIdentifier` +- `SERVERPOD_PASSWORD_appleRedirectUri` +- `SERVERPOD_PASSWORD_appleTeamId` +- `SERVERPOD_PASSWORD_appleKeyId` +- `SERVERPOD_PASSWORD_appleKey` +- `SERVERPOD_PASSWORD_appleWebRedirectUri` (optional, for Web support when using server callback route) +- `SERVERPOD_PASSWORD_appleAndroidPackageIdentifier` (optional, for Android support) + ::: Then, extend the abstract endpoint to expose it on the server: @@ -128,7 +194,16 @@ import 'package:serverpod_auth_idp_server/providers/apple.dart'; class AppleIdpEndpoint extends AppleIdpBaseEndpoint {} ``` -Finally, run `serverpod generate` to generate the client code and create a migration to initialize the database for the provider. More detailed instructions can be found in the general [identity providers setup section](../../setup#identity-providers-configuration). +Run `serverpod generate` to generate the client code, then create and apply a database migration to initialize the provider's tables: + +```bash +serverpod generate +dart run bin/main.dart --apply-migrations +``` + +:::note +Skipping the migration will cause the server to crash at runtime when the Apple provider tries to read or write user data. More detailed instructions can be found in the general [identity providers setup section](../../setup#identity-providers-configuration). +::: ### Basic configuration options @@ -150,7 +225,9 @@ For more details on configuration options, see the [configuration section](./con The `serverpod_auth_idp_flutter` package implements the sign-in logic using [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple). The documentation for this package should in most cases also apply to the Serverpod integration. -_Note that Sign in with Apple may not work on some versions of the Simulator (iOS 13.5 works). This issue doesn't affect real devices._ +:::note +Sign in with Apple may not work correctly on all Simulator versions. If you run into issues during development, test on a physical device to confirm whether the problem is Simulator-specific. +::: ### iOS and macOS @@ -174,8 +251,24 @@ To enable this: 1. Add the `androidPackageIdentifier` to your `AppleIdpConfig` (or the `appleAndroidPackageIdentifier` key in `passwords.yaml`). This must match your app's Android package name (e.g., `com.example.app`). 2. Configure the redirect URI in your Apple Developer Portal to point to your server's callback route (e.g., `https://example.com/auth/callback`). +3. Register the `signinwithapple` URI scheme in your `AndroidManifest.xml`: + +```xml + + + + + + + + +``` -No additional client-side Android configuration is needed beyond what the [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) package requires. +:::warning +This intent filter is required. Without it, the OAuth callback never returns to your app and sign-in silently hangs. +::: ### Web @@ -188,6 +281,10 @@ To enable this: If `webRedirectUri` is not configured, Web callbacks to the server route will fail. +:::warning +All redirect URIs must use **HTTPS**. Apple rejects HTTP URLs, including `localhost`. For local development, expose your server over HTTPS using a tunnelling service, like ngrok or Cloudflare Tunnel. +::: + ## Present the authentication UI ### Initializing the `AppleSignInService` @@ -233,9 +330,23 @@ AppleSignInWidget( ) ``` +This renders an Apple sign-in button like this: + +![Apple sign-in button](/img/authentication/providers/apple/3-button.png) + The widget automatically handles: + - Apple Sign-In flow for iOS, macOS, Android, and Web. - Token management. - Underlying Apple Sign-In package error handling. For details on how to customize the Apple Sign-In UI in your Flutter app, see the [customizing the UI section](./customizing-the-ui). + +:::warning +**Apple sends the user's email address and full name only on the first sign-in.** On all subsequent sign-ins, neither is included in the response. If your server does not persist them during that first authentication, they cannot be retrieved later. + +Use the `sub` claim (the stable user identifier) to identify users. Do not use the email address, as it may change when a user updates their "Hide My Email" settings. For more information, see [Authenticating users with Sign in with Apple](https://developer.apple.com/documentation/sign_in_with_apple/authenticating-users-with-sign-in-with-apple). + +--- + +If you run into issues, see the [troubleshooting guide](./troubleshooting). diff --git a/versioned_docs/version-3.4.0/06-concepts/11-authentication/04-providers/04-apple/02-configuration.md b/versioned_docs/version-3.4.0/06-concepts/11-authentication/04-providers/04-apple/02-configuration.md index e0d883d7..964ae368 100644 --- a/versioned_docs/version-3.4.0/06-concepts/11-authentication/04-providers/04-apple/02-configuration.md +++ b/versioned_docs/version-3.4.0/06-concepts/11-authentication/04-providers/04-apple/02-configuration.md @@ -2,10 +2,6 @@ This page covers configuration options for the Apple identity provider beyond the basic setup. - - ## Web Routes Configuration Apple Sign-In requires web routes for handling callbacks and notifications. These routes must be configured both on Apple's side and in your Serverpod server. @@ -13,6 +9,7 @@ Apple Sign-In requires web routes for handling callbacks and notifications. Thes The `revokedNotificationRoutePath` is the path that Apple will call when a user revokes their authorization. The `webAuthenticationCallbackRoutePath` is the path that Apple will call when a user completes the sign-in process. These routes are configured in the `pod.configureAppleIdpRoutes()` method: + ```dart pod.configureAppleIdpRoutes( revokedNotificationRoutePath: '/hooks/apple-notification', @@ -20,11 +17,18 @@ pod.configureAppleIdpRoutes( ); ``` -### Configuring Apple Sign-In on the App +- `revokedNotificationRoutePath` (default: `'/hooks/apple-notification'`): The path Apple calls when a user revokes authorization. Register this URL in your Apple Developer Portal for server-to-server notifications. +- `webAuthenticationCallbackRoutePath` (default: `'/auth/callback'`): The path Apple redirects to after the user completes web-based sign-in. Must match the return URL registered on your Service ID. + +:::note +When a user revokes access from their Apple ID settings, Apple sends a notification to `revokedNotificationRoutePath`. Serverpod receives this notification automatically. You are responsible for invalidating any active sessions for that user in your own application logic. +::: + +## Configuring Apple Sign-In on the app -Apple Sign-In requires additional configuration for web and Android platforms. On native Apple platforms (iOS/macOS), the configuration is automatically handled by the underlying `sign_in_with_apple` package through Xcode capabilities. +Apple Sign-In requires additional configuration for web and Android platforms. On native Apple platforms (iOS/macOS), the configuration is handled automatically by the underlying `sign_in_with_apple` package through Xcode capabilities. -#### Passing Configuration in Code +### Passing configuration in code You can pass the configuration directly when initializing the Apple Sign-In service: @@ -37,18 +41,23 @@ client.auth.initializeAppleSignIn( The `serviceIdentifier` is your Apple Services ID (configured in Apple Developer Portal), and the `redirectUri` is the callback URL that Apple will redirect to after authentication (must match the URL configured on the server). +Both parameters are optional. If not supplied, the provider falls back to the corresponding `--dart-define` build variable: + +- `serviceIdentifier` → `APPLE_SERVICE_IDENTIFIER` +- `redirectUri` → `APPLE_REDIRECT_URI` + :::note -These parameters are only required for web and Android platforms. On native Apple platforms (iOS/macOS), they are ignored and the configuration from Xcode capabilities is used instead. +These parameters are only required for web and Android platforms. On native Apple platforms (iOS/macOS), they are ignored, and the configuration from Xcode capabilities is used instead. ::: -#### Using Environment Variables +### Using Environment Variables -Alternatively, you can pass configuration during build time using the `--dart-define` option. The Apple Sign-In provider supports the following environment variables: +Alternatively, you can pass configuration during build time using the `--dart-define` option: - `APPLE_SERVICE_IDENTIFIER`: The Apple Services ID. - `APPLE_REDIRECT_URI`: The redirect URI for authentication callbacks. -If `serviceIdentifier` and `redirectUri` values are not supplied when initializing the service, the provider will automatically fetch them from these environment variables. +If you do not supply `serviceIdentifier` and `redirectUri` values when initializing the service, the provider will automatically fetch them from these environment variables. **Example usage:** @@ -63,8 +72,34 @@ This approach is useful when you need to: - Manage configuration separately for different platforms (Android, Web) in a centralized way. - Avoid committing sensitive configuration to version control. -- Configure different credentials for different build environments (development, staging, production). +- Configure different credentials for different build environments, like development, staging, and production. :::tip You can also set these environment variables in your IDE's run configuration or CI/CD pipeline to avoid passing them manually each time. ::: + +## `AppleIdpConfig` parameter reference + +| Parameter | Type | Required | `passwords.yaml` key | Description | +| --- | --- | --- | --- | --- | +| `serviceIdentifier` | `String` | Yes (Android/Web) | `appleServiceIdentifier` | The Services ID identifier (e.g. `com.example.service`). Used as the OAuth client ID for Android and Web. Not required for iOS/macOS-only setups. | +| `bundleIdentifier` | `String` | Yes | `appleBundleIdentifier` | The App ID bundle identifier (e.g. `com.example.app`). Used as the client ID for native Apple platform sign-in. | +| `redirectUri` | `String` | Yes (Android/Web) | `appleRedirectUri` | The server callback route Apple redirects to after sign-in (e.g. `https://example.com/auth/callback`). Must be HTTPS and match the return URL registered on your Service ID. | +| `teamId` | `String` | Yes | `appleTeamId` | The 10-character Team ID from your Apple Developer account (e.g. `ABC123DEF4`). Used to sign the client secret JWT. | +| `keyId` | `String` | Yes | `appleKeyId` | The Key ID of the Sign in with Apple private key (e.g. `XYZ789ABC0`). | +| `key` | `String` | Yes | `appleKey` | The raw contents of the `.p8` private key file, including the `-----BEGIN PRIVATE KEY-----` header and footer. Serverpod uses this to generate a short-lived client secret JWT on each request. Do not pre-generate the JWT yourself. | +| `webRedirectUri` | `String?` | Web only | `appleWebRedirectUri` | The web app URL that the browser is redirected to after the server receives Apple's callback. This is required when using the server callback route for Web. | +| `androidPackageIdentifier` | `String?` | Android only | `appleAndroidPackageIdentifier` | The Android package name (e.g. `com.example.app`). When set, the callback route redirects Android clients back to the app via an intent URI using the `signinwithapple` scheme. | + +### Environment Variable equivalents + +All `passwords.yaml` keys can be set as environment variables by prefixing with `SERVERPOD_PASSWORD_`: + +- `appleServiceIdentifier` → `SERVERPOD_PASSWORD_appleServiceIdentifier` +- `appleBundleIdentifier` → `SERVERPOD_PASSWORD_appleBundleIdentifier` +- `appleRedirectUri` → `SERVERPOD_PASSWORD_appleRedirectUri` +- `appleTeamId` → `SERVERPOD_PASSWORD_appleTeamId` +- `appleKeyId` → `SERVERPOD_PASSWORD_appleKeyId` +- `appleKey` → `SERVERPOD_PASSWORD_appleKey` +- `appleWebRedirectUri` → `SERVERPOD_PASSWORD_appleWebRedirectUri` +- `appleAndroidPackageIdentifier` → `SERVERPOD_PASSWORD_appleAndroidPackageIdentifier` diff --git a/versioned_docs/version-3.4.0/06-concepts/11-authentication/04-providers/04-apple/04-troubleshooting.md b/versioned_docs/version-3.4.0/06-concepts/11-authentication/04-providers/04-apple/04-troubleshooting.md new file mode 100644 index 00000000..4f022fad --- /dev/null +++ b/versioned_docs/version-3.4.0/06-concepts/11-authentication/04-providers/04-apple/04-troubleshooting.md @@ -0,0 +1,129 @@ +# Troubleshooting + +This page helps you identify common Sign in with Apple failures, understand why they occur, and resolve them. For Apple's full list of OAuth error codes, see [TN3107: Resolving Sign in with Apple response errors](https://developer.apple.com/documentation/technotes/tn3107-resolving-sign-in-with-apple-response-errors). + +## Setup checklist + +Go through this before investigating a specific error. Most problems come from a missed step. + +* [ ] Enable **Sign in with Apple** on your App ID at [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list). +* [ ] Add **Sign in with Apple** under Signing & Capabilities in Xcode (*iOS/macOS only*). +* [ ] Create a **Service ID** and link it to your App ID (*Android and Web only*). +* [ ] Confirm the **return URL** on the Service ID uses `https://` (not `http://` or `localhost`). +* [ ] Make sure **`appleKey`** in your config holds the raw `.p8` file contents (not a pre-generated JWT). +* [ ] Double-check the **`.p8` key** is indented consistently under `appleKey: |` in `passwords.yaml`. +* [ ] Run **`serverpod generate`** after adding the Apple provider, and apply migrations using `--apply-migrations`. +* [ ] Call **`pod.configureAppleIdpRoutes(...)`** on the server before the pod starts. +* [ ] Add the **`signinwithapple`** intent filter to `AndroidManifest.xml` (*Android only*). +* [ ] Add **Apple's mail servers** to your SPF record if you email users who might use Hide My Email. + +## Sign-in fails with `invalid_client` every time + +**Problem:** Every authentication attempt gives an `invalid_client` error from Apple. + +**Cause:** The `appleKey` value in `passwords.yaml` is not indented correctly. The key gets corrupted during parsing. The server starts without error, but Apple sees an invalid signature. + +**Resolution:** Paste the raw `.p8` key under `appleKey` with consistent indentation. All lines of the key must line up with the one that starts `-----BEGIN PRIVATE KEY-----`. For example: + +```yaml +appleKey: | + -----BEGIN PRIVATE KEY----- + MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg... + -----END PRIVATE KEY----- +``` + +Alternatively, set `appleKey` as an environment variable to avoid YAML indentation entirely. See [Environment Variable equivalents](./configuration#environment-variable-equivalents) in the configuration page. + +## Sign-in starts failing with `invalid_client` after months of success + +**Problem:** Sign-in was working for months, then suddenly fails with `invalid_client` and you haven't changed code. + +**Cause:** `appleKey` has a pre-generated client secret JWT, not the raw `.p8` key. Apple makes JWTs expire after six months. When it expires, all sign-ins fail. + +**Resolution:** Replace any JWT in `appleKey` with the raw `.p8` private key (include the full header and footer). Serverpod will create fresh short-lived JWTs automatically. No need to handle JWTs yourself. See [Creating a client secret](https://developer.apple.com/documentation/accountorganizationaldatasharing/creating-a-client-secret). + +## Sign-in hangs on Android + +**Problem:** The OAuth flow opens a browser, but never returns to the app. Sign-in seems to finish but the app doesn't get the callback. + +**Cause:** The `signinwithapple` URI scheme isn't registered in `AndroidManifest.xml`, so Android drops the callback. + +**Resolution:** Add this activity to `AndroidManifest.xml`: + +```xml + + + + + + + + +``` + +## Server crashes on first Apple sign-in with "no such table" + +**Problem:** The server builds and starts, but crashes when a user tries Apple sign-in. The error cites a missing table (like `serverpod_auth_idp_apple_account`). + +**Cause:** `serverpod generate` has been run, but you didn't create or apply the accompanying database migration. + +**Resolution:** Create and apply the migration: + +```bash +serverpod generate +dart run bin/main.dart --apply-migrations +``` + +## Apple rejects the redirect URI with `invalid_request` + +**Problem:** The web OAuth flow fails with `invalid_request` and Apple's error page says the redirect URI is invalid. + +**Cause:** You're using HTTP instead of HTTPS for the redirect. Apple requires HTTPS and does not allow `localhost`. + +**Resolution:** Always use an HTTPS URL for your redirect. For local development, run your server behind an HTTPS tunnel. Register the tunneled `https://` URL as your return URL in Apple's Developer Portal, and update `appleRedirectUri` in `passwords.yaml` to match. + +## Emails aren't delivered to some users + +**Problem:** Transactional emails (password resets, notifications) work for most people, but some never receive them. + +**Cause:** Some users chose Apple's "Hide My Email" during sign-in. Mail to relay addresses like `@privaterelay.appleid.com` fails if your domain's SPF record doesn't include Apple's mail servers. + +**Resolution:** Add Apple's mail servers to your SPF record. See [Configure private email relay service](https://developer.apple.com/help/account/configure-app-capabilities/configure-private-email-relay-service/) for SPF settings and instructions. + +## User email is `null` after sign-in + +**Problem:** The user's email is missing or `null` after sign in, or it's present on first sign-in but missing after that. + +**Cause:** Apple sends the email address and name only once, during initial authorization. After that, only the `sub` claim is provided. If you didn't save the email the first time, you can't get it again unless the user disconnects and reconnects your app. + +**Resolution:** Make sure your server stores the user's email on their first sign-in. Use `sub` as the main user identifier, not email (which can change if the user updates Hide My Email). See [Authenticating users with Sign in with Apple](https://developer.apple.com/documentation/sign_in_with_apple/authenticating-users-with-sign-in-with-apple). + +## iOS sign-in prompt doesn't show + +**Problem:** Tapping the Sign in with Apple button on iOS has no effect, or the Apple authentication UI never appears. + +**Cause:** The App ID isn't set up with the Sign in with Apple capability, or your Xcode project isn't using that App ID. + +**Resolution:** + +1. In [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list), find your App ID. Check that **Sign in with Apple** is enabled under Capabilities. +2. In Xcode, select your target, open **Signing & Capabilities**, and check that **Sign in with Apple** is listed. If not, click **+ Capability** to add it. +3. Download and install your new provisioning profile if needed. + +## Sign-in does not work in the iOS Simulator + +**Problem:** Sign in with Apple silently fails or the native authentication sheet does not appear when running in the iOS Simulator, but works fine on a physical device. + +**Cause:** Some Simulator versions do not fully support the native Sign in with Apple flow. This is a known Simulator limitation, not a code or configuration issue. + +**Resolution:** Test on a physical device to confirm the problem is Simulator-specific. If sign-in works on a real device, no changes are needed. + +## User stays signed in after removing Apple access + +**Problem:** A user removes your app from Apple ID settings (`Settings > [your name] > Sign-In & Security > Sign in with Apple > Stop Using Apple ID`) but is still logged in to your app. + +**Cause:** Your server receives Apple's revocation notification but doesn't terminate the user's active sessions. + +**Resolution:** When you receive a revocation notification at the route set using `pod.configureAppleIdpRoutes(revokedNotificationRoutePath: ...)`, look up the user by the `sub` value in the payload and invalidate all their sessions. See [Processing changes for Sign in with Apple accounts](https://developer.apple.com/documentation/signinwithapple/processing-changes-for-sign-in-with-apple-accounts) for how the notification works.