- Published on
How to Add Apple Sign In to Flutter Application
- Authors
- Name
- Rosa Tiara
Introduction
If your Flutter app targets iOS users, you need to add Apple Sign In. The App Store requires it whenever your app includes third-party login options like Google or Facebook.
Why Apple Sign In?
- Users can choose to hide their email address
- Face ID/Touch ID integration for quick authentication
- Required if you offer other social sign-in options
- Works on iOS, macOS, watchOS, and tvOS
Prerequisites
Before we start, make sure you have:
- Flutter SDK installed (2.0 or higher recommended)
- An Apple Developer account (required for setting up capabilities)
- Xcode installed (for iOS development)
- Basic understanding of Flutter and Dart
Step 1: Add Dependencies
First, add the sign_in_with_apple package to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
sign_in_with_apple: ^7.0.1
Run flutter pub get to install the package.
Step 2: Configure Your Apple Developer Account
Enable Sign in with Apple Capability
- Go to Apple Developer Portal and sign in
- Navigate to Certificates, Identifiers & Profiles
- Select your app's Identifier
- Enable Sign in with Apple capability
- Click Save
Configure Your Xcode Project
-
Open your Flutter project in Xcode:
open ios/Runner.xcworkspace -
Select your project in the navigator
-
Go to Signing & Capabilities tab
-
Click + Capability
-
Add Sign in with Apple
Your Xcode project is now configured!
Step 3: Basic Implementation
Let's create a simple Apple Sign In button and handle the authentication flow.
Create the Sign In Button
import 'package:flutter/material.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
class AppleSignInButton extends StatelessWidget {
const AppleSignInButton({Key? key}) : super(key: key);
Future<void> _handleAppleSignIn() async {
try {
final credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
);
// Successfully signed in
print('User ID: ${credential.userIdentifier}');
print('Email: ${credential.email}');
print('Name: ${credential.givenName} ${credential.familyName}');
// Handle the authentication with your backend here
} catch (error) {
print('Error during Apple Sign In: $error');
}
}
Widget build(BuildContext context) {
return SignInWithAppleButton(
onPressed: _handleAppleSignIn,
);
}
}
Understanding the Credential Response
When a user successfully signs in, you will receive an AuthorizationCredentialAppleID object that contains:
userIdentifier- unique user ID (use this as the primary identifier)email- user's email (may be a proxy email if they chose to hide their real email)givenName- user's first namefamilyName- user's last nameidentityToken- JWT token for backend verificationauthorizationCode- one-time use authorization code
Important: Name and email are only provided on the first sign-in, so it's best to store them immediately.
Step 4: Custom Styling
You can customize the Apple Sign In button to match your app's design:
SignInWithAppleButton(
onPressed: _handleAppleSignIn,
style: SignInWithAppleButtonStyle.black, // or .white, .whiteOutline
borderRadius: BorderRadius.circular(8),
iconAlignment: IconAlignment.center,
height: 50,
text: 'Sign in with Apple', // Custom text
)
Step 5: Backend Integration
For production apps, you should verify the identity token with your backend:
Future<void> _signInWithApple() async {
try {
final credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
);
// Send to your backend for verification
final response = await _verifyWithBackend(
identityToken: credential.identityToken,
authorizationCode: credential.authorizationCode,
userIdentifier: credential.userIdentifier,
);
// Handle successful authentication
if (response.success) {
// Navigate to home screen
Navigator.pushReplacementNamed(context, '/home');
}
} catch (error) {
// Handle error
_showErrorDialog(error.toString());
}
}
Future<AuthResponse> _verifyWithBackend({
required String? identityToken,
required String? authorizationCode,
required String userIdentifier,
}) async {
// Implement your backend verification here
// Your backend should verify the identityToken with Apple's servers
final response = await http.post(
Uri.parse('https://your-api.com/auth/apple'),
body: jsonEncode({
'identity_token': identityToken,
'authorization_code': authorizationCode,
'user_identifier': userIdentifier,
}),
);
return AuthResponse.fromJson(jsonDecode(response.body));
}
Step 6: Handle Sign Out
Don't forget to implement sign-out functionality:
Future<void> _signOut() async {
// Clear your local session
await _clearUserSession();
// Navigate to login screen
Navigator.pushReplacementNamed(context, '/login');
}
Note: Apple doesn't provide a sign-out API. You only need to clear your app's local session.
Step 7: Check Authentication Status
Check if a user is currently signed in when your app starts:
class AuthService {
// Store user ID in secure storage
Future<bool> isUserSignedIn() async {
final userId = await _secureStorage.read(key: 'user_id');
return userId != null;
}
// Get credential state
Future<CredentialState> getCredentialState(String userIdentifier) async {
final state = await SignInWithApple.getCredentialState(userIdentifier);
return state;
}
}
The credential state can be:
authorized- User is signed inrevoked- User revoked accessnotFound- No credential foundtransferred- Account was transferred
Best Practices
1. Store User Information Immediately
Apple only provides the user's name and email on the first sign-in. Store them right away:
Future<void> _storeUserInfo(AuthorizationCredentialAppleID credential) async {
// Only available on first sign-in
if (credential.givenName != null) {
await _secureStorage.write(key: 'given_name', value: credential.givenName);
}
if (credential.familyName != null) {
await _secureStorage.write(key: 'family_name', value: credential.familyName);
}
if (credential.email != null) {
await _secureStorage.write(key: 'email', value: credential.email);
}
// Always available
await _secureStorage.write(key: 'user_id', value: credential.userIdentifier);
}
2. Handle Revocation
Revocation = the act of withdrawing, cancelling, or invalidating a previously granted permission, right, access, or credential.
Listen for credential revocation and handle it:
void _checkCredentialState(String userIdentifier) async {
final credentialState = await SignInWithApple.getCredentialState(userIdentifier);
if (credentialState == CredentialState.revoked) {
// User revoked access, sign them out
await _signOut();
_showMessage('Your Apple ID sign-in was revoked. Please sign in again.');
}
}
3. Use Secure Storage
Always use secure storage (like flutter_secure_storage) for storing sensitive information:
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
final _secureStorage = FlutterSecureStorage();
// Store
await _secureStorage.write(key: 'user_id', value: userId);
// Read
final userId = await _secureStorage.read(key: 'user_id');
// Delete
await _secureStorage.delete(key: 'user_id');
4. Error Handling
Implement clear error handling:
Future<void> _handleAppleSignIn() async {
try {
final credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
);
await _processCredential(credential);
} on SignInWithAppleAuthorizationException catch (e) {
// Handle specific Apple Sign In errors
switch (e.code) {
case AuthorizationErrorCode.canceled:
print('User canceled the sign-in');
break;
case AuthorizationErrorCode.failed:
print('Authorization failed');
break;
case AuthorizationErrorCode.invalidResponse:
print('Invalid response');
break;
case AuthorizationErrorCode.notHandled:
print('Not handled');
break;
case AuthorizationErrorCode.unknown:
print('Unknown error');
break;
}
} catch (e) {
// Handle other errors
print('Unexpected error: $e');
}
}
Testing
On Physical Device
Apple Sign In only works on physical devices, not on simulators. To test:
- Build and run on a physical iOS device
- Make sure your device is signed in to iCloud
- Test the sign-in flow
Test with Sandbox Account
For testing without affecting your production data:
- Go to Settings → Apple ID → Password & Security → Apps Using Apple ID
- Create a sandbox account in App Store Connect
- Sign in with the sandbox account on your device
Common Issues and Solutions
Issue 1: "Invalid Client" Error
Solution: Make sure the Bundle ID in Xcode matches the one configured in Apple Developer Portal.
Issue 2: Button Not Showing
Solution: Check that:
- You're testing on a physical device (not simulator)
- Sign in with Apple capability is enabled in Xcode
- Your app's identifier has the capability enabled in Developer Portal
Issue 3: Email or Name is Null
Solution: This is expected after the first sign-in. Apple only provides this information once. Make sure to store it on the first authentication.
Issue 4: "Unsupported" Error on Android
Solution: If you need cross-platform support, use the webAuthenticationOptions parameter:
final credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
webAuthenticationOptions: WebAuthenticationOptions(
clientId: 'your.bundle.id',
redirectUri: Uri.parse('https://your-redirect-uri.com/callback'),
),
);
Links
- Apple Sign In Official Documentation
- sign_in_with_apple package
- Apple Developer Portal
Happy coding! 🚀