As per Apple guidelines, apps that are offering sign in with other social services also need to provide the option of apple sign-in. I think it is very convenient to offer (Apple Sign In) on iOS as you would offer Google Sign In on Android, as your iOS users already have an Apple ID and can use it to sign in with your App.
In this post, we are going to add Apple Sign In to our Flutter & Firebase app. We will use the sign_in_with_apple Flutter package available on pub.dev.
Prerequisites
Xcode installed
An Apple Developer Account
An iOS 13.x device or simulator, signed in with an Apple ID
Firebase Project for the App
Firebase Setup
We need to add iOS App to the Firebase project. Note the bundle id; we will use it later.
Download the GoogleService-Info.plist file
Enable Apple authentication
Apple Setup
Open the Identifiers page on the Apple developer website here and click the (+) icon.
Select App IDs and click Continue.
The type is App. click Continue.
Set the bundle Id similar to what we used in the firebase project. Check on the (Sign In with Apple) capability, then select Continue.
Click Register to complete Apple configuration.
Project Setup
Let's start by creating an App using the command below using Terminal.
flutter create flutter_apple_signin
Update pubspec.yaml as below to add the required packages
dependencies:
flutter:
sdk: flutter
firebase_core: "^0.7.0"
firebase_auth: "^0.20.0+1"
sign_in_with_apple: ^2.5.2
crypto: ^2.1.5
provider: ^4.3.3
Right-click on the ios folder and select Open in Xcode.
On the Signing & Capabilities tab, configure the signing by setting the Team as per your Apple developer account. Make sure to use the same bundle Id we used in the firebase project.
Add "Sign In With Apple" as a new Capability:
Set the Deployment target to iOS 13
Right-click on the Runner Project and select Add Files to "Runner"
Select the GoogleService-Info.plist filer we downloaded from Firebase and select Add.
App Implementation
Create the authentication_provider.dart file below in the (lib\providers) folder. We will use it to sign in with Apple.
import 'dart:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
class AuthenticationProvider with ChangeNotifier {
final FirebaseAuth _firebaseAuth;
AuthenticationProvider(this._firebaseAuth);
Stream<User> get authStateChanges => _firebaseAuth.authStateChanges();
Future<void> signOut() async {
await _firebaseAuth.signOut();
}
/// Generates a cryptographically secure random nonce, to be included in a
/// credential request.
String generateNonce([int length = 32]) {
final charset =
'0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
final random = Random.secure();
return List.generate(length, (_) => charset[random.nextInt(charset.length)])
.join();
}
/// Returns the sha256 hash of [input] in hex notation.
String sha256ofString(String input) {
final bytes = utf8.encode(input);
final digest = sha256.convert(bytes);
return digest.toString();
}
Future<User> signInWithApple() async {
// To prevent replay attacks with the credential returned from Apple, we
// include a nonce in the credential request. When signing in in with
// Firebase, the nonce in the id token returned by Apple, is expected to
// match the sha256 hash of `rawNonce`.
final rawNonce = generateNonce();
final nonce = sha256ofString(rawNonce);
try {
// Request credential for the currently signed in Apple account.
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: nonce,
);
print(appleCredential.authorizationCode);
// Create an `OAuthCredential` from the credential returned by Apple.
final oauthCredential = OAuthProvider("apple.com").credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);
// Sign in the user with Firebase. If the nonce we generated earlier does
// not match the nonce in `appleCredential.identityToken`, sign in will fail.
final authResult =
await _firebaseAuth.signInWithCredential(oauthCredential);
final displayName =
'${appleCredential.givenName} ${appleCredential.familyName}';
final userEmail = '${appleCredential.email}';
final firebaseUser = authResult.user;
print(displayName);
await firebaseUser.updateProfile(displayName: displayName);
await firebaseUser.updateEmail(userEmail);
return firebaseUser;
} catch (exception) {
print(exception);
}
}
}
Create the login_screen.dart file below in the (lib\screens) folder. This will display the Apple SignIn button, which will use the method (signInWithApple) from the AuthenticationProvider.
import 'package:flutter/material.dart';
import 'package:flutter_apple_signin/providers/authentication_provider.dart';
import 'package:provider/provider.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
class LoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SignInWithAppleButton(
style: SignInWithAppleButtonStyle.black,
iconAlignment: IconAlignment.center,
onPressed: () {
context.read<AuthenticationProvider>().signInWithApple();
},
)
],
),
),
);
}
}
On main.dart, we will watch the Firebase User object to determine the login or logout page.
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_apple_signin/providers/authentication_provider.dart';
import 'package:flutter_apple_signin/screens/login_screen.dart';
import 'package:flutter_apple_signin/screens/logout_screen.dart';
import 'package:provider/provider.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (ctx) => AuthenticationProvider(FirebaseAuth.instance),
),
StreamProvider(
create: (BuildContext context) {
return context.read<AuthenticationProvider>().authStateChanges;
},
)
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.purple,
accentColor: Colors.deepOrange,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
routes: {},
),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final firebaseUser = context.watch<User>();
return Scaffold(
appBar: AppBar(
title: Text('Apple Sign In'),
),
body: firebaseUser != null ? LogoutPage() : LoginPage());
}
}
The implementation is complete. Let's run the App.
Check the code here
Follow me on Twitter for more tips about #coding, #learning, #technology...etc.
Check my channel on YouTube
Check my Apps on Google Play
Cover image Aditya Joshi on Unsplash
Top comments (16)
Hey, good post.
I've been running into the following errors when I try to launch on a physical iOS device:
[core] Authorization failed: Error Domain=AKAuthenticationError Code=-7026 "(null)"
flutter: SignInWithAppleAuthorizationError(AuthorizationErrorCode.unknown, The operation couldn’t be completed. (com.apple.AuthenticationServices.AuthorizationError error 1000.))
And the following error when I try launching on an iOS simulator:
SignInWithAppleAuthorizationError(AuthorizationErrorCode.unknown, The operation couldn’t be completed. (com.apple.AuthenticationServices.AuthorizationError error 1000.))
I have everything set up correctly with Sign in With Apple capability and set the iOS deployment target to 13.
Any idea what is happening here?
Thanks
Make sure to add "SignIn with Apple" capability in your plist file
Also if you are testing on a simulator, use a simulator of iOS 13.5 or lower...
I got it working, I'm not sure exactly what the issue was.
I have the capability enabled in XCode, but don't see it in my plist file- what is the key value pair for sign in with apple in the plist file?
The following was generated in my Runner.entitlements file for Apple Sign In:
aps-environment
development
com.apple.developer.applesignin
Default
Do I still need the capability in the plist file?
Hi,
How did you fix the error.
I am getting the same error whilst running it on Real deveice and also on simulator
Hello!! nice post!! Ive been doing the same but in the apple sign in screen after selected my email, full name and password a message appeared saying "login not completed" I tried to debug but no error show, its like it isnt making the validation. Any tips? im so stuck! Thank you! :)
Are you testing on simulator? if yes, then try using a simulator of iOS 13.5 or lower...
Thank you for your reply! I am testing on a physical device. I made it work now, I was missing on developer console, under Certificates, Identifiers & Profiles -> Keys. Adding the apple sign in key. I just did it on xcode but apparently wasnt enough
Thank you again and your post is so helpfull!
Great...
Hi ,I could not run the code
For solutions, see dart.dev/go/unsound-null-safety
lib/authentication_provider.dart:37:16: Error: A non-null value must be returned since the return type 'User' doesn't allow null.
- 'User' is from 'package:firebase_auth/firebase_auth.dart' ('../../../../Developer/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_auth-0.20.1/lib/firebase_auth.dart').
Future signInWithApple() async {
^
Failed to package /Users/mortezakolivand/Desktop/Flutter Projects/AppleIdTest/appleidtest.
Command PhaseScriptExecution failed with a nonzero exit code
note: Using new build system
note: Building targets in parallel
note: Planning build
note: Analyzing workspace
note: Constructing build description
note: Build preparation complete
Could not build the application for the simulator.
Error launching application on iPhone 12.
I have this error
Thanks, again.
this is good,
i want to get phone number use firebase apple login,
i calling function all data get but phone number is null,
"Your app requires users to provide their name phone number after using Sign in with Apple. This information is already provided by the Authentication Services framework. "
and my application are rejected,
after login with apple show this data,
User(displayName: null, email: **, isEmailVerified: true, isAnonymous: false, metadata: UserMetadata(creationTime: 2024-07-01 07:31:33.412Z, lastSignInTime: 2024-07-01 09:36:07.276Z), phoneNumber: null, photoURL: null, providerData, [UserInfo(displayName: null, email: *, phoneNumber: null, photoURL: null, providerId: apple.com, uid: "")], refreshToken: "")
how to solve this problem please help
have you got the solution?
Thanks for the great article. You saved my day!
What should I do for sign Up Page not sign IN?
I guess it will be the same, you just create user in firestore yourself with credential provided by Apple sign in
You mean creating an Apple account? I don't think that is supported by Firebase.