DEV Community

Cover image for Flutter Firebase Authentication: Apple Sign In
Offline Programmer
Offline Programmer

Posted on • Updated on

Flutter Firebase Authentication: Apple Sign In

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.

Screen Shot 2021-02-16 at 4.19.52 PM

Download the GoogleService-Info.plist file

Screen Shot 2021-02-16 at 4.30.08 PM

Enable Apple authentication

Screen Shot 2021-02-16 at 9.09.05 PM

Apple Setup

Open the Identifiers page on the Apple developer website here and click the (+) icon.

Screen Shot 2021-02-16 at 8.40.34 PM

Select App IDs and click Continue.

Screen Shot 2021-02-16 at 8.43.02 PM

The type is App. click Continue.

Screen Shot 2021-02-16 at 8.46.05 PM

Set the bundle Id similar to what we used in the firebase project. Check on the (Sign In with Apple) capability, then select Continue.

Screen Shot 2021-02-16 at 8.52.22 PM

Click Register to complete Apple configuration.

Screen Shot 2021-02-16 at 8.56.20 PM

Project Setup

Let's start by creating an App using the command below using Terminal.




flutter create flutter_apple_signin



Enter fullscreen mode Exit fullscreen mode

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



Enter fullscreen mode Exit fullscreen mode

Right-click on the ios folder and select Open in Xcode.

Screen Shot 2021-02-16 at 3.38.44 PM

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.

Screen Shot 2021-02-16 at 3.47.46 PM

Add "Sign In With Apple" as a new Capability:

Screen Shot 2021-02-16 at 3.46.05 PM

Set the Deployment target to iOS 13

Screen Shot 2021-02-16 at 8.07.57 PM

Right-click on the Runner Project and select Add Files to "Runner"

Screen Shot 2021-02-16 at 4.34.22 PM

Select the GoogleService-Info.plist filer we downloaded from Firebase and select Add.

Screen Shot 2021-02-16 at 4.36.56 PM

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);
    }
  }
}





Enter fullscreen mode Exit fullscreen mode

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();
              },
            )
          ],
        ),
      ),
    );
  }
}



Enter fullscreen mode Exit fullscreen mode

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());
  }
}





Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
tjgrapes profile image
Ashton Jones

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

Collapse
 
offlineprogrammer profile image
Offline Programmer

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...

Collapse
 
tjgrapes profile image
Ashton Jones • Edited

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?

Thread Thread
 
koketso_koks profile image
AppsByKoketso

Hi,

How did you fix the error.

I am getting the same error whilst running it on Real deveice and also on simulator

Collapse
 
paramo profile image
Paramo

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! :)

Collapse
 
offlineprogrammer profile image
Offline Programmer

Are you testing on simulator? if yes, then try using a simulator of iOS 13.5 or lower...

Collapse
 
paramo profile image
Paramo

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!

Thread Thread
 
offlineprogrammer profile image
Offline Programmer

Great...

Collapse
 
morteza_kolivand_4c554f06 profile image
Morteza Kolivand

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

Collapse
 
umutcoskun profile image
Umut Çağdaş Coşkun

Thanks, again.

Collapse
 
deepak_suthar_34a868a84af profile image
Deepak Suthar • Edited

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

Collapse
 
payal_godara_fbf6a8598428 profile image
Payal Godara

have you got the solution?

Collapse
 
umutcoskun profile image
Umut Çağdaş Coşkun

Thanks for the great article. You saved my day!

Collapse
 
647338bacbbb426 profile image
Ankur Saini

What should I do for sign Up Page not sign IN?

Collapse
 
skobe profile image
Aleksandr Skobeltcyn

I guess it will be the same, you just create user in firestore yourself with credential provided by Apple sign in

Collapse
 
offlineprogrammer profile image
Offline Programmer

You mean creating an Apple account? I don't think that is supported by Firebase.