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 9939f1a7..f840eae0 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 @@ -1,20 +1,10 @@ # Setup -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. - -:::caution -You need to install the auth module before you continue, see [Setup](../../setup). -::: +Sign in with Apple requires a subscription to the [Apple Developer Program](https://developer.apple.com/programs/), even for development and testing. ## 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 | — | — | +All platforms require an App ID and a Sign in with Apple key. Android and Web additionally require a Service ID. ### Register your App ID @@ -85,10 +75,11 @@ Each primary App ID can have a maximum of two private keys. If you reach the lim ### Store your credentials -Add the credentials to `config/passwords.yaml`: +Your server's `config/passwords.yaml` already has `development:`, `staging:`, and `production:` sections from the project template. Add the Apple credentials to the `development:` section: ```yaml development: + # ... existing keys (database, redis, serviceSecret, etc.) ... appleServiceIdentifier: 'com.example.service' appleBundleIdentifier: 'com.example.app' appleRedirectUri: 'https://example.com/auth/callback' @@ -104,88 +95,58 @@ development: appleAndroidPackageIdentifier: 'com.example.app' ``` -:::warning -**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. +For production, add the same keys to the `production:` section of `passwords.yaml`, or set the corresponding `SERVERPOD_PASSWORD_` environment variables on your production server. -**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. +:::tip +Paste the raw `.p8` file contents as-is. Do not pre-generate a JWT -- Serverpod handles that internally. If sign-in fails, see the [troubleshooting guide](./troubleshooting). +::: ## Server-side configuration -After creating your credentials, you need to configure the Apple identity provider on your main `server.dart` file by setting the `AppleIdpConfig` as a `identityProviderBuilders` in your `pod.initializeAuthServices()` configuration: +### Add the Apple identity provider + +Your server's `server.dart` file (e.g., `my_project_server/lib/server.dart`) should already contain a `pod.initializeAuthServices()` call if your project was created with the Serverpod project template (`serverpod create`). If it's not there, see [Setup](../../setup) first to configure the auth module and JWT settings. + +Add the Apple import and `AppleIdpConfigFromPasswords()` to the existing `identityProviderBuilders` list: ```dart -import 'package:serverpod/serverpod.dart'; -import 'package:serverpod_auth_idp_server/core.dart'; import 'package:serverpod_auth_idp_server/providers/apple.dart'; +``` -void run(List args) async { - final pod = Serverpod( - args, - Protocol(), - Endpoints(), - ); - - // Configure Apple identity provider - pod.initializeAuthServices( - tokenManagerBuilders: [ - JwtConfigFromPasswords(), - ], - identityProviderBuilders: [ - AppleIdpConfig( - serviceIdentifier: pod.getPassword('appleServiceIdentifier')!, - bundleIdentifier: pod.getPassword('appleBundleIdentifier')!, - redirectUri: pod.getPassword('appleRedirectUri')!, - teamId: pod.getPassword('appleTeamId')!, - keyId: pod.getPassword('appleKeyId')!, - key: pod.getPassword('appleKey')!, - // Optional: Required only for Web support when using server callback route. - webRedirectUri: pod.getPassword('appleWebRedirectUri'), - // Optional: Required only for Android support. - androidPackageIdentifier: pod.getPassword('appleAndroidPackageIdentifier'), - ), - ], - ); - - // Configure web routes for Apple Sign-In - // Paths must match paths configured on Apple's developer portal. - // The method's parameters are optional and defaults to the values below. - pod.configureAppleIdpRoutes( - revokedNotificationRoutePath: '/hooks/apple-notification', - webAuthenticationCallbackRoutePath: '/auth/callback', - ); - - await pod.start(); -} +```dart +pod.initializeAuthServices( + tokenManagerBuilders: [ + JwtConfigFromPasswords(), + ], + identityProviderBuilders: [ + // ... any existing providers (e.g., EmailIdpConfigFromPasswords) ... + AppleIdpConfigFromPasswords(), + ], +); ``` -:::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) - -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) +`AppleIdpConfigFromPasswords()` automatically loads the credentials from the Apple keys in `config/passwords.yaml` (or the corresponding `SERVERPOD_PASSWORD_` environment variables). + +### Configure web routes + +Apple Sign-In requires web routes for handling callbacks and revocation notifications. Add this call before `pod.start()`: + +```dart +pod.configureAppleIdpRoutes( + revokedNotificationRoutePath: '/hooks/apple-notification', + webAuthenticationCallbackRoutePath: '/auth/callback', +); +``` + +The `webAuthenticationCallbackRoutePath` must match the **Return URL** you registered on your Service ID. The `revokedNotificationRoutePath` is called by Apple when a user revokes access from their Apple ID settings. +:::tip +If you need more control over how the credentials are loaded, you can use `AppleIdpConfig(...)` with manual `pod.getPassword()` calls instead. See the [customizations](./customizations) page for details. ::: -Then, extend the abstract endpoint to expose it on the server: +### Create the endpoint + +Create a new endpoint file in your server project (e.g., `my_project_server/lib/src/auth/apple_idp_endpoint.dart`). Extending the base class registers the sign-in methods with your server so the Flutter client can call them: ```dart import 'package:serverpod_auth_idp_server/providers/apple.dart'; @@ -193,7 +154,9 @@ import 'package:serverpod_auth_idp_server/providers/apple.dart'; class AppleIdpEndpoint extends AppleIdpBaseEndpoint {} ``` -Run `serverpod generate` to generate the client code, then create and apply a database migration to initialize the provider's tables: +### Generate code and apply migrations + +Run the following commands from your server project directory (e.g., `my_project_server/`) to generate client code and apply the database migration: ```bash serverpod generate @@ -205,25 +168,9 @@ dart run bin/main.dart --apply-migrations 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 - -- `serviceIdentifier`: Required. The service identifier for the Sign in with Apple project. -- `bundleIdentifier`: Required. The bundle ID of the Apple-native app using Sign in with Apple. -- `redirectUri`: Required. The redirect URL used for 3rd party platforms (e.g., Android, Web). -- `teamId`: Required. The team identifier of the parent Apple Developer account. -- `keyId`: Required. The ID of the key associated with the Sign in with Apple service. -- `key`: Required. The secret contents of the private key file received from Apple. - -When using Web or Android, you can also configure the following optional parameters: - -- `webRedirectUri`: The URL where the browser is redirected after the server receives Apple's callback on Web. Required for Web support when using the server callback route. -- `androidPackageIdentifier`: The Android package identifier for the app. Required for Apple Sign In to work on Android. When configured, the callback route automatically redirects Android clients back to the app using an intent URI. - -For more details on configuration options, see the [configuration section](./configuration). - ## Client-side configuration -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. +The `serverpod_auth_idp_flutter` package uses [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) under the hood for platform-specific sign-in flows. :::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. @@ -247,11 +194,7 @@ Enable the Sign in with Apple capability in your Xcode project: Apple Sign In on Android works through a web-based OAuth flow. When the user completes authentication, Apple redirects to your server's callback route, which then redirects back to your app using an Android intent URI with the `signinwithapple` scheme. -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`: +The redirect URI and `appleAndroidPackageIdentifier` were already configured in the [Store your credentials](#store-your-credentials) and [Service ID](#create-a-service-id-android-and-web-only) steps. The only remaining step is to register the `signinwithapple` URI scheme in your `AndroidManifest.xml`: ```xml ` tag: -1. Configure the redirect URI in your Apple Developer Portal to match your server's callback route (e.g., `https://example.com/auth/callback`). -2. Set `webRedirectUri` in `AppleIdpConfig` (or `appleWebRedirectUri` in `passwords.yaml`) to the Web URL that should receive the callback parameters (e.g., `https://example.com/auth/apple-complete`). +```html + +``` -If `webRedirectUri` is not configured, Web callbacks to the server route will fail. +The redirect URI and `appleWebRedirectUri` were already configured in the [Store your credentials](#store-your-credentials) and [Service ID](#create-a-service-id-android-and-web-only) steps. :::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. @@ -287,28 +229,33 @@ All redirect URIs must use **HTTPS**. Apple rejects HTTP URLs, including `localh ## Present the authentication UI -### Initializing the `AppleSignInService` +### Initialize the Apple sign-in service -To use the AppleSignInService, you need to initialize it in your main function. The initialization is done from the `initializeAppleSignIn()` extension method on the `FlutterAuthSessionManager`. +In your Flutter app's `main.dart` file (e.g., `my_project_flutter/lib/main.dart`), the template already sets up the `Client` and calls `client.auth.initialize()`. Add `client.auth.initializeAppleSignIn()` right after it: ```dart -import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart'; -import 'package:your_client/your_client.dart'; +client.auth.initialize(); +client.auth.initializeAppleSignIn(); +``` -final client = Client('http://localhost:8080/') - ..authSessionManager = FlutterAuthSessionManager(); +On **Web and Android**, the sign-in service needs your Service ID and redirect URI. Pass them as build-time environment variables using `--dart-define`: -void main() { - client.auth.initialize(); - client.auth.initializeAppleSignIn(); -} +```bash +flutter run \ + -d "" \ + --dart-define="APPLE_SERVICE_IDENTIFIER=com.example.service" \ + --dart-define="APPLE_REDIRECT_URI=https://example.com/auth/callback" ``` -### Using AppleSignInWidget +Use the same values you configured in the [Service ID](#create-a-service-id-android-and-web-only) and [Store your credentials](#store-your-credentials) steps. + +You can also pass the values directly as parameters instead. See the [customizations page](./customizations#configuring-apple-sign-in-on-the-app) for details. + +### Add the sign-in widget If you have configured the `SignInWidget` as described in the [setup section](../../setup#present-the-authentication-ui), the Apple identity provider will be automatically detected and displayed in the sign-in widget. -You can also use the `AppleSignInWidget` to include the Apple authentication flow in your own custom UI. +You can also use the `AppleSignInWidget` directly in your widget tree to include the Apple authentication flow in your own custom UI: ```dart import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart'; @@ -343,10 +290,9 @@ The widget automatically handles: 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). - ---- +Apple sends the user's email and name only on the **first sign-in**. If your server does not persist them during that first authentication, they cannot be retrieved later. +::: +:::tip 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-customizations.md similarity index 60% rename from docs/06-concepts/11-authentication/04-providers/04-apple/02-configuration.md rename to docs/06-concepts/11-authentication/04-providers/04-apple/02-customizations.md index 7ab368c8..bf631109 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-customizations.md @@ -1,11 +1,36 @@ -# Configuration +# Customizations -This page covers configuration options for the Apple identity provider beyond the basic setup. +This page covers additional configuration options for the Apple identity provider beyond the basic setup. ## Configuration options Below is a non-exhaustive list of some of the most common configuration options. For more details on all options, check the `AppleIdpConfig` in-code documentation. +### Loading Apple credentials + +You can initialize the Apple identity provider in two ways: + +**From passwords.yaml (recommended):** + +```dart +final appleIdpConfig = AppleIdpConfigFromPasswords(); +``` + +**Manually, providing each credential explicitly:** + +```dart +final appleIdpConfig = AppleIdpConfig( + serviceIdentifier: pod.getPassword('appleServiceIdentifier')!, + bundleIdentifier: pod.getPassword('appleBundleIdentifier')!, + redirectUri: pod.getPassword('appleRedirectUri')!, + teamId: pod.getPassword('appleTeamId')!, + keyId: pod.getPassword('appleKeyId')!, + key: pod.getPassword('appleKey')!, + webRedirectUri: pod.getPassword('appleWebRedirectUri'), + androidPackageIdentifier: pod.getPassword('appleAndroidPackageIdentifier'), +); +``` + ### Reacting to account creation You can use the `onAfterAppleAccountCreated` callback to run logic after a new Apple account has been created and linked to an auth user. This callback is only invoked for new accounts, not for returning users. @@ -13,8 +38,7 @@ You can use the `onAfterAppleAccountCreated` callback to run logic after a new A This callback is complimentary to the [core `onAfterAuthUserCreated` callback](../../working-with-users#reacting-to-the-user-created-event) to perform side-effects that are specific to a login on this provider - like storing analytics, sending a welcome email, or storing additional data. ```dart -final appleIdpConfig = AppleIdpConfig( - // ... required parameters ... +final appleIdpConfig = AppleIdpConfigFromPasswords( onAfterAppleAccountCreated: ( session, authUser, @@ -34,7 +58,7 @@ This callback runs inside the same database transaction as the account creation. If you need to assign Serverpod scopes based on provider account data, note that updating the database alone (via `AuthServices.instance.authUsers.update()`) is **not enough** for the current login session. The token issuance uses the in-memory `authUser.scopes`, which is already set before this callback runs. You would need to update `authUser.scopes` as well for the scopes to be reflected in the issued tokens. For assigning scopes at creation time, consider using `onBeforeAuthUserCreated` to set scopes based on data collected earlier in the flow. ::: -## Web Routes Configuration +### 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. @@ -53,14 +77,14 @@ pod.configureAppleIdpRoutes( - `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. +When a user revokes access from their Apple ID settings, Apple sends a notification to `revokedNotificationRoutePath`. You are responsible for invalidating any active sessions for that user in your own application logic. ::: -## Configuring Apple Sign-In on the app +### 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 handled automatically by the underlying `sign_in_with_apple` package through Xcode capabilities. +On web and Android platforms, you must supply a service identifier and redirect URI. If no values are provided programmatically, the provider falls back to reading from `--dart-define` build variables. To set them programmatically, you can use the following methods. -### Passing configuration in code +#### Passing Configuration in Code You can pass the configuration directly when initializing the Apple Sign-In service: @@ -71,25 +95,22 @@ 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: +The `serviceIdentifier` is your Apple Services ID, and the `redirectUri` is the callback URL that Apple redirects to after authentication (must match the URL configured on the server). -- `serviceIdentifier` → `APPLE_SERVICE_IDENTIFIER` -- `redirectUri` → `APPLE_REDIRECT_URI` +This approach is useful when you need to manage configuration for different platforms in your Dart code. :::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. ::: -### Using Environment Variables +#### Using Environment Variables -Alternatively, you can pass configuration during build time using the `--dart-define` option: +Alternatively, you can pass configuration during build time using the `--dart-define` option. The Apple Sign-In provider supports the following build-time variables: -- `APPLE_SERVICE_IDENTIFIER`: The Apple Services ID. -- `APPLE_REDIRECT_URI`: The redirect URI for authentication callbacks. +- `APPLE_SERVICE_IDENTIFIER`: The Services ID used as OAuth client ID on Android and Web +- `APPLE_REDIRECT_URI`: The callback URL Apple redirects to after authentication -If you do not supply `serviceIdentifier` and `redirectUri` values when initializing the service, the provider will automatically fetch them from these environment variables. +If `serviceIdentifier` and `redirectUri` are not supplied when initializing the service, the provider will automatically read them from these variables. **Example usage:** @@ -102,36 +123,23 @@ flutter run \ 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, like development, staging, and production. +- 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) :::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 +## All configuration parameters | 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. | +| `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. | | `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` +| `redirectUri` | `String` | Yes (Android/Web) | `appleRedirectUri` | The server callback route Apple redirects to after sign-in. 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. Used to sign the client secret JWT. | +| `keyId` | `String` | Yes | `appleKeyId` | The Key ID of the Sign in with Apple private key. | +| `key` | `String` | Yes | `appleKey` | The raw contents of the `.p8` private key file, including the `-----BEGIN PRIVATE KEY-----` header and footer. Do not pre-generate the JWT yourself. | +| `webRedirectUri` | `String?` | Web only | `appleWebRedirectUri` | The web app URL the browser is redirected to after the server receives Apple's callback. | +| `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. | 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 index 535ef46f..740fb789 100644 --- 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 @@ -6,14 +6,28 @@ This page helps you identify common Sign in with Apple failures, explains why th Go through this before investigating a specific error. Most problems come from a missed step. +#### Apple Developer Portal + * [ ] 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`. +* [ ] Create a **Sign in with Apple key** and download the `.p8` file. + +#### Server + +* [ ] Add the Apple credentials to `config/passwords.yaml` with the raw `.p8` file contents (not a pre-generated JWT). +* [ ] Double-check the **`.p8` key** is indented consistently under `appleKey: |`. +* [ ] Add `AppleIdpConfigFromPasswords()` to `identityProviderBuilders` in `server.dart`. * [ ] Call **`pod.configureAppleIdpRoutes(...)`** on the server before the pod starts. +* [ ] Create an `AppleIdpEndpoint` file in `lib/src/auth/`. +* [ ] Run **`serverpod generate`**, then apply migrations using `--apply-migrations`. + +#### Client + +* [ ] Add `client.auth.initializeAppleSignIn()` after `client.auth.initialize()` in your Flutter app's `main.dart`. +* [ ] Add **Sign in with Apple** under Signing & Capabilities in Xcode (*iOS/macOS only*). +* [ ] Add the **Apple JS SDK** script to `web/index.html` (*Web only*). +* [ ] Pass **`APPLE_SERVICE_IDENTIFIER`** and **`APPLE_REDIRECT_URI`** via `--dart-define` (*Web and Android only*). * [ ] 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. @@ -32,7 +46,7 @@ appleKey: | -----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. +Alternatively, set `appleKey` via the `SERVERPOD_PASSWORD_appleKey` environment variable to avoid YAML indentation entirely. ## Sign-in starts failing with `invalid_client` after months of success @@ -42,6 +56,40 @@ Alternatively, set `appleKey` as an environment variable to avoid YAML indentati **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 fails with `invalid_grant` + +**Problem:** Authentication fails with an `invalid_grant` error from Apple. + +**Cause:** Apple's authorization codes are single-use and expire after approximately 10 minutes. This error occurs when: + +* The authorization code was already exchanged (e.g. the request was retried after a network failure). +* The server clock is significantly out of sync, causing the client secret JWT to appear expired before Apple processes it. +* The identity token nonce does not match what the server expects. + +**Resolution:** + +* Do not retry requests that carry an Apple authorization code. If the flow fails, restart it from the beginning. +* Ensure your server's system clock is synchronized via NTP. A drift of more than a few seconds will cause JWT validation to fail on Apple's side. +* If the nonce mismatch is the cause, verify that the nonce generated on the client matches what the server uses during token validation. + +## Wrong identifier passed for web or Android sign-in + +**Problem:** Sign-in on Android or Web fails immediately, or Apple returns `invalid_client` / `invalid_request` even though credentials look correct. + +**Cause:** There are two separate identifiers in Apple's system and they are easy to mix up: + +* **App ID** (`bundleIdentifier`) -- the bundle identifier of your iOS/macOS app (e.g. `com.example.app`). Used for native Apple platform sign-in only. +* **Services ID** (`serviceIdentifier`) -- a separate identifier you create in the Apple Developer Portal specifically for web and Android OAuth (e.g. `com.example.service`). This acts as the OAuth client ID. + +Passing the App ID bundle identifier where the Services ID is expected will cause Apple to reject the request. + +**Resolution:** Check `passwords.yaml` and confirm: + +* `appleServiceIdentifier` is set to your **Services ID** (the one created under Identifiers → Services IDs). +* `appleBundleIdentifier` is set to your **App ID** bundle identifier. + +If you use `--dart-define`, confirm `APPLE_SERVICE_IDENTIFIER` is the Services ID, not the bundle ID. + ## 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. @@ -120,6 +168,28 @@ dart run bin/main.dart --apply-migrations **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. +## Web sign-in fails with `TypeError: type ... is not a subtype of type 'JSObject'` + +**Problem:** Clicking the Apple button on Web throws a `TypeError` mentioning `JSObject` or a minified type like `minified:CM`. + +**Cause:** The Apple JS SDK is not loaded. The `sign_in_with_apple` package calls `AppleID.auth.init()` on the page, but that function only exists after Apple's script is loaded in the HTML. + +**Resolution:** Add the Apple JS SDK to your Flutter app's `web/index.html` inside the `` tag: + +```html + +``` + +The `crossorigin="anonymous"` attribute is needed because Flutter's service worker sets a strict Cross-Origin Embedder Policy that blocks scripts without it. + +## macOS sign-in shows "Sign Up Not Completed" + +**Problem:** The native Sign in with Apple sheet appears on macOS, but immediately shows "Sign Up Not Completed" without completing authentication. + +**Cause:** The macOS app sandbox entitlement conflicts with `ASAuthorizationController`. When `com.apple.security.app-sandbox` is enabled alongside `com.apple.developer.applesignin`, the authorization flow fails silently. + +**Resolution:** In your macOS entitlements file (e.g., `macos/Runner/DebugProfile.entitlements`), remove the app sandbox entitlement or ensure it does not block the Sign in with Apple flow. Test without the sandbox first to confirm it is the cause, then re-add only the sandbox entitlements you need. + ## 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. diff --git a/static/img/authentication/providers/apple/3-button.png b/static/img/authentication/providers/apple/3-button.png index 49f90eff..51405fea 100644 Binary files a/static/img/authentication/providers/apple/3-button.png and b/static/img/authentication/providers/apple/3-button.png differ