Providers

Apple Provider

Native Sign in with Apple, backed by the sign_in_with_apple SDK, no browser flow.

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)

Earlier versions shipped a browser-based 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

pubspec.yaml
dependencies:
  authyra_flutter: ^0.1.0
  authyra_apple: ^0.1.0

Setup

1. Apple Developer Portal

  1. Enable the Sign in with Apple capability for your app identifier.
  2. iOS and macOS need no further configuration beyond the capability.
  3. 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

lib/main.dart
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 fieldSourceNotes
iduserIdentifier, or identityToken sub claim on AndroidStable Apple User ID
emailemail, or identityToken email claimMay be an Apple-relayed private address
namegivenName + familyNameFirst 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.

Copyright © 2026