Apple Provider
AppleProvider implements Sign in with Apple using Apple's OAuth 2.0 Authorization Code flow. It handles the Apple-specific quirks automatically: ES256 JWT client secrets generated per-request, user profile decoded from the id_token (no userinfo endpoint), and first-sign-in name capture from the callback URL.
Package: authyra_flutter
Apple-specific behaviour
| Quirk | What Authyra does |
|---|---|
| No static client secret | Generates a short-lived ES256 JWT from your .p8 key on every token request |
| No userinfo endpoint | Decodes sub, email, and email_verified from the id_token JWT |
| Name only on first sign-in | Extracts name from the user callback parameter and folds it into AuthUser |
| PKCE + nonce | Both enabled by default for replay protection |
| Refresh token valid 6 months | supportsRefresh: true; Apple does not rotate the refresh token |
user.name immediately after the first successful sign-in — you will not receive it again.Constructor
AppleProvider({
required AppleOAuthConfig config,
Dio? dio,
})
| Parameter | Description |
|---|---|
config | AppleOAuthConfig — credentials and redirect URI |
dio | Optional Dio instance for testing or shared interceptors |
The provider ID is 'apple'.
AppleOAuthConfig
AppleOAuthConfig({
required String clientId,
required String teamId,
required String keyId,
required String privateKeyPem,
required String redirectUri,
List<String> scopes = const ['name', 'email'],
Duration clientSecretValidity = const Duration(days: 180),
Duration timeout = const Duration(minutes: 5),
})
| Field | Default | Description |
|---|---|---|
clientId | required | Services ID from the Apple Developer Portal (e.g. com.example.app.web) |
teamId | required | 10-character Team ID from the top-right of the Developer Portal |
keyId | required | Key ID of the Sign in with Apple key |
privateKeyPem | required | Full contents of the .p8 file including header/footer |
redirectUri | required | HTTPS URI registered in the Developer Portal |
scopes | ['name', 'email'] | Apple supports only name and email |
clientSecretValidity | 180 days | Lifetime of the generated ES256 JWT (Apple max: 180 days) |
timeout | 5 min | Max wait before throwing AuthenticationCancelledException |
Setup
1. Configure in Apple Developer Portal
Services ID (used as clientId):
- In Identifiers create a Services ID.
- Enable Sign in with Apple and click Configure.
- Add your
redirectUridomain under Web Domain and the full URI under Return URLs. Apple requires HTTPS — localhost is not permitted.
Key (used for keyId and privateKeyPem):
- In Keys create a new key.
- Enable Sign in with Apple and configure it for your App ID.
- Download the
.p8file — you can only download it once. - Note the Key ID shown next to the key.
2. Add the provider
import 'package:app_links/app_links.dart';
import 'package:authyra_flutter/authyra_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final appleProvider = AppleProvider(
config: AppleOAuthConfig(
clientId: 'com.example.app.service',
teamId: 'AB12CD34EF',
keyId: 'XYZXYZXYZX',
privateKeyPem: const String.fromEnvironment('APPLE_PRIVATE_KEY'),
redirectUri: 'https://api.example.com/auth/apple/callback',
),
);
// Apple redirects to an HTTPS URI — use Universal Links or your backend
// to relay the callback back to the app via a custom scheme.
OAuth2CallbackHandler.registerProvider('https', appleProvider);
AppLinks().uriLinkStream.listen(OAuth2CallbackHandler.handleCallback);
await Authyra.initialize(
client: AuthyraClient(
providers: [appleProvider],
storage: SecureAuthStorage(),
config: const AuthConfig(autoRefresh: true),
),
);
runApp(const MyApp());
}
privateKeyPem in source control. Load it from an environment variable, a secrets manager, or an encrypted asset file excluded from version control.3. Deep-link relay
Apple requires an HTTPS redirectUri. For native apps, set up a server endpoint that:
- Receives the
codeandstatefrom Apple. - Relays them to the app via a custom URI scheme:
myapp://auth/apple/callback?code=…&state=….
Then register the custom scheme in your platform manifests and pass it as the scheme to OAuth2CallbackHandler.
Sign in
try {
final user = await Authyra.instance.signIn('apple');
print('Hello, ${user.name ?? user.email ?? 'there'}!');
} on AuthenticationCancelledException {
// User tapped "Cancel" on the Apple sign-in sheet
} on AuthenticationFailedException catch (e) {
print('Apple sign-in failed: $e');
}
User fields
AuthUser field | Source | Notes |
|---|---|---|
id | id_token → sub | Stable Apple User ID |
email | id_token → email | May be an Apple-relayed private address |
name | user callback param | First sign-in only — persist immediately |
metadata['email_verified'] | id_token | |
metadata['given_name'] | user callback param | First sign-in only |
metadata['family_name'] | user callback param | First sign-in only |
Token refresh
Apple refresh tokens are valid for up to 6 months and are not rotated on use. AppleProvider sets supportsRefresh: true. When AuthyraClient detects that the access token is approaching expiry, it silently exchanges the refresh token:
await Authyra.instance.refreshSession();
Persisting the user's name
Apple sends name data only on the first sign-in. After a successful sign-in, immediately persist user.name alongside the user record in your backend. On subsequent sign-ins, restore the name from your backend rather than relying on Authyra's session.
final user = await Authyra.instance.signIn('apple');
if (user.name != null) {
// First sign-in — save name to your backend now
await myApi.updateUser(id: user.id, name: user.name!);
}
Troubleshooting
invalid_client from Apple
The ES256 JWT was rejected. Check:
teamIdis your 10-character team identifier (not the App ID prefix).keyIdmatches the key shown in Apple Developer → Keys.privateKeyPemincludes the full header and footer lines.clientIdis the exact Services ID registered in Apple Developer.
Name is null after first sign-in
The user parameter Apple sends in the callback URL was not captured. This happens if the callback deep-link was not delivered to OAuth2CallbackHandler before the token exchange completed. Ensure AppLinks().uriLinkStream.listen(OAuth2CallbackHandler.handleCallback) is registered before Authyra.initialize.
redirectUri not accepted by Apple
Apple only accepts HTTPS URIs. The domain must be verified in your Services ID configuration under Web Domain.
See also
- OAuth2Provider → — base class
- ProxyOAuthProvider → — backend relay pattern (avoids HTTPS URI requirement)
- Flutter Setup → — deep-link wiring