Apple Provider
AppleProvider implements Sign in with Apple using the native sign_in_with_apple SDK: the platform sheet on iOS/macOS, a Chrome Custom Tab on Android, a popup on web. It does not open a browser-based OAuth2 flow the way GoogleProvider or GitHubProvider do.
Package: authyra_apple (separate from authyra_flutter)
AppleProvider inside authyra_flutter. It has been removed in favor of this package. App Store Review Guideline 4.8 requires the native Sign in with Apple UI whenever another social login (Google, Facebook, ...) is offered on iOS, a browser-based OAuth2 flow does not satisfy that requirement.Install
dependencies:
authyra_flutter: ^0.1.0
authyra_apple: ^0.1.0
Setup
1. Apple Developer Portal
- Enable the Sign in with Apple capability for your app identifier.
- iOS and macOS need no further configuration beyond the capability.
- Android and web additionally need a Services ID: create one at Identifiers, enable Sign in with Apple, and configure its return URL.
2. Add the provider
import 'package:authyra_apple/authyra_apple.dart';
import 'package:authyra_flutter/authyra_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final appleProvider = AppleProvider(
webAuthenticationOptions: WebAuthenticationOptions(
clientId: 'com.example.app.service',
redirectUri: Uri.parse('https://example.com/callbacks/sign_in_with_apple'),
),
);
await Authyra.initialize(
client: AuthyraClient(
providers: [appleProvider],
storage: SecureAuthStorage(),
),
);
runApp(const MyApp());
}
No deep-link wiring is required: the native SDK resolves the sign-in directly, unlike OAuth2Provider-based providers which need OAuth2CallbackHandler (see Deep Links →).
3. Android manifest
Add the intent filter documented in sign_in_with_apple's README so the Custom Tab can redirect back into the app. This is native-side configuration, no Dart code involved.
Two ways to use it
Identity only, no backend exchange
The default constructor returns Apple's identity directly. No application session token is set; the raw credential (identityToken, authorizationCode) is stored in AuthAccount.providerData, never in accessToken/refreshToken.
final appleProvider = AppleProvider(
webAuthenticationOptions: myWebAuthOptions,
);
final user = await Authyra.instance.signIn('apple');
This is enough if you hand the credential to Firebase's or Supabase's own Apple sign-in, which accept it directly.
With a backend exchange
Use AppleProvider.withExchange to forward the credential to your backend and return real application session tokens:
final appleProvider = AppleProvider.withExchange(
webAuthenticationOptions: myWebAuthOptions,
exchangeCredential: (credential) async {
final res = await myApi.post('/auth/apple', body: {
'authorizationCode': credential.authorizationCode,
'identityToken': credential.identityToken,
});
if (res.statusCode != 200) return null;
return AuthSignInResult(
user: AuthUser(id: res.data['userId'], email: res.data['email']),
accessToken: res.data['accessToken'],
refreshToken: res.data['refreshToken'],
expiresAt: DateTime.parse(res.data['expiresAt']),
);
},
);
AppleCredential carries userIdentifier, identityToken, authorizationCode, and (first sign-in only) email, givenName, familyName.
Sign in
try {
final user = await Authyra.instance.signIn('apple');
print('Hello, ${user.name ?? user.email ?? 'there'}!');
} on AuthenticationCancelledException {
// User dismissed the Apple sheet
} on AuthenticationFailedException catch (e) {
print('Apple sign-in failed: $e');
}
User fields (identity-only mode)
AuthUser field | Source | Notes |
|---|---|---|
id | userIdentifier, or identityToken sub claim on Android | Stable Apple User ID |
email | email, or identityToken email claim | May be an Apple-relayed private address |
name | givenName + familyName | First sign-in only, persist immediately |
metadata['email_verified'] | identityToken claim |
With .withExchange, the user comes entirely from your backend's response instead.
Token refresh
supportsRefresh is always false. Renewing an Apple-issued token requires a server-held ES256 client secret; this provider never asks you to embed that key in the app. If you need silent refresh, implement it against your own backend's refresh endpoint, the backend that received authorizationCode via exchangeCredential is the one that holds the client secret and can call Apple's token endpoint.
Persisting the user's name
Apple sends givenName/familyName only on the first sign-in. Persist them to your backend immediately:
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!);
}
Known limitation
On Android, if the user closes the Chrome Custom Tab without completing the flow, the underlying sign_in_with_apple call never resolves. This is a limitation of that package, not something Authyra can work around; give users a way to back out from your own UI if this matters for your app.
Troubleshooting
SignInWithAppleNotSupportedException
Sign in with Apple requires iOS 13+ or macOS 10.15+. Not thrown on Android or web.
Callback never completes on Android/web
Confirm webAuthenticationOptions.clientId matches your Services ID exactly and redirectUri matches its configured return URL in the Apple Developer Portal.
Name is null after first sign-in
The app didn't persist user.name from the very first successful sign-in. Apple never sends it again; you must have saved it to your backend at that point.