Providers

Apple Provider

Sign in with Apple — OAuth 2.0 with PKCE, dynamic ES256 client secret, and id_token user extraction.

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

QuirkWhat Authyra does
No static client secretGenerates a short-lived ES256 JWT from your .p8 key on every token request
No userinfo endpointDecodes sub, email, and email_verified from the id_token JWT
Name only on first sign-inExtracts name from the user callback parameter and folds it into AuthUser
PKCE + nonceBoth enabled by default for replay protection
Refresh token valid 6 monthssupportsRefresh: true; Apple does not rotate the refresh token
Apple sends the user's name and email only on the first sign-in. Persist user.name immediately after the first successful sign-in — you will not receive it again.

Constructor

AppleProvider({
  required AppleOAuthConfig config,
  Dio? dio,
})
ParameterDescription
configAppleOAuthConfig — credentials and redirect URI
dioOptional 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),
})
FieldDefaultDescription
clientIdrequiredServices ID from the Apple Developer Portal (e.g. com.example.app.web)
teamIdrequired10-character Team ID from the top-right of the Developer Portal
keyIdrequiredKey ID of the Sign in with Apple key
privateKeyPemrequiredFull contents of the .p8 file including header/footer
redirectUrirequiredHTTPS URI registered in the Developer Portal
scopes['name', 'email']Apple supports only name and email
clientSecretValidity180 daysLifetime of the generated ES256 JWT (Apple max: 180 days)
timeout5 minMax wait before throwing AuthenticationCancelledException

Setup

1. Configure in Apple Developer Portal

Services ID (used as clientId):

  1. In Identifiers create a Services ID.
  2. Enable Sign in with Apple and click Configure.
  3. Add your redirectUri domain under Web Domain and the full URI under Return URLs. Apple requires HTTPS — localhost is not permitted.

Key (used for keyId and privateKeyPem):

  1. In Keys create a new key.
  2. Enable Sign in with Apple and configure it for your App ID.
  3. Download the .p8 file — you can only download it once.
  4. Note the Key ID shown next to the key.

2. Add the provider

lib/main.dart
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());
}
Never hardcode privateKeyPem in source control. Load it from an environment variable, a secrets manager, or an encrypted asset file excluded from version control.

Apple requires an HTTPS redirectUri. For native apps, set up a server endpoint that:

  1. Receives the code and state from Apple.
  2. 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 fieldSourceNotes
idid_tokensubStable Apple User ID
emailid_tokenemailMay be an Apple-relayed private address
nameuser callback paramFirst sign-in only — persist immediately
metadata['email_verified']id_token
metadata['given_name']user callback paramFirst sign-in only
metadata['family_name']user callback paramFirst 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:

  • teamId is your 10-character team identifier (not the App ID prefix).
  • keyId matches the key shown in Apple Developer → Keys.
  • privateKeyPem includes the full header and footer lines.
  • clientId is 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

Copyright © 2026