sign in with Apple

Sign in with Apple in Flutter apps

In late 2019 Apple has released iOS 13 and with it a new requirement has been added to applications submitted to AppStore. Now whenever you want to upload an app that uses 3rd party sign-in methods (such as with Facebook, Google, Twitter, etc.) it must also employ Sign in with Apple authorization.

Introduction

In this post I will tell you all you need to know about Sign in with Apple and how to implement it using Flutter. I will first introduce you to this new method of authorization, then you’ll learn how to prepare project’s configuration, implement iOS part and make it work with Flutter code.

About Sign in with Apple

What is Sign in with Apple?

Sign in with Apple is a method of authorization very similar to all other methods supplied by 3rd party providers. As a user, you press a button with Apple logo and Sign in with Apple title, sign in to your Apple account and log into the application you want to use. Pretty simple.

As a developer you must be aware of some details that make this method stand out among the others. These will be addressed in later part of this post, but first I would like to focus on something else.

When is Sign in with Apple necessary?

As mentioned earlier, Sign in with Apple is mandatory for every iOS application submitted to AppStore that utilizes a 3rd party authentication method. Since this is only required by Apple’s App Store you may safely hide it when submitting an Android version of your application to Google Play.

It is worth mentioning that Sign in with Apple logic, provided by standard libraries, works only with iOS 13. So you may also hide it for users that have not yet upgraded their OS to the latest version.

What to be aware of?

When a user logs in to your application via Sign in with Apple they first sign in to their Apple account by using either their phone password or biometric methods (TouchID, FaceID). During signing in, two options are presented for the user.

First is about the name. He can choose to either give your application this information or to change it to anything they want to. So you cannot count on getting your user’s real name and surname when he’s using Sign in with Apple.

Second is about their email. He can choose to either give you his real email address associated with his Apple account or he can choose to have his email hidden. If he does, then you will acquire an email address generated by Apple (which could for example look like this: wxdkqhqjxc@privaterelay.appleid.com). By default, if you send an email to this address it will be relayed to the user’s real address. However this can be turned off in setting at any time by the user, so you cannot rely on your messages always reaching your target.

To wrap things up, here are 3 most important things to remember:

  1. Users can decide to change their name
  2. They can hide their real email and provide an auto-generated one
  3. They can decide to stop relaying messages to their real account if they desire

There is also one very important thing that you must remember. Whether a user has decided to share his real data with you or not, when you request this information from Apple, it will give it to you only once, when user signs in for the first time.

Implementation

Configuration

Run your application on an iPhone or a simulator. Then, open the Xcode project. You can do it by typing open ios/Runner.xcworkspace in the terminal. Open project’s main configuration file (1), open Signing & Capabilities tab (2) and assign your Apple Development team to project (3). Then click on + Capability button (4).

In a window that appears start typing sign in to filter possible options (1). Double-click on the only option that remains (2). This concludes the configuration, you can now use Sign in with Apple in your application.

Adding button

About Sign in with Apple button

To allow your users to authenticate using Sign in with Apple you must first add a proper button to your application. The design of this button has been prepared by Apple and you should use it, or you risk your Application being rejected from AppStore.

The button should consist of Apple logo andSign in with Apple title translated to your application’s language. There are three allowed versions. You should choose the one that fits your application best.

  1. Black logo and title on white background
  2. White logo and title on black background
  3. Black logo and title on white background with a black border

You can set the button’s height and corner radius to any value you want. Ideally, these should be the same as all the other sign in buttons in your application. Sign in with Apple button should be visible immediately on your login page, user should not have to scroll down to find it. It should preferably be above all other sign in buttons.

You can perform some other minor modification on the button’s original design (for example you can omit the title and leave only the logo inside a square button) if it looks better in your application. For details refer to this website.

Implementation

There are 2 ways to add Sign in with Apple button to your application:

  1. Redraw it in Flutter
  2. Use iOS view

I strongly encourage you to use the second method. Not only will it spare you the trouble of reimplementing this view as a Flutter widget, using the default button also has useful out-of-the-box logic such as auto-translation to your app current language.

But how do you place an iOS view inside a Flutter widget? Is it even possible? Well if it wasn’t I wouldn’t be writing this paragraph. Flutter has a widget called UiKitView, which allows you quite easily to embed any iOS view in Flutter widget. Pretty neat, isn’t it?

Finally, after all this introduction and configuration, we can get to coding.

First, in a dart code, find a build() method of a widget that you want to place Sign in with Apple button in and add the following code:
SizedBox(
 width: MediaQuery.of(context).size.width * 0.8,
 height: 60.0,
 child: UiKitView(viewType: 'AppleSignIn'),
),

First we’re adding a SizedBox with a height and width set. This can be exchanged to any other widget that has a fixed size, for example a Container. This fixed size is important, because Flutter must know how much space it must reserve to render the iOS view. Then we pass a UiKitView as a child of this widget and pass it one string argument. In our case we just pass AppleSignIn. You will learn it’s significance in a while.

Then move to iOS code. For the purpose of this post I assume that you’re using Swift language. Open AppDelegate.swift file. In this post I will put all my iOS code there, you can however split it into multiple files if you wish. If you add new Swift files to your iOS project Xcode will find them automatically without any imports.

First you must import the AuthenticationServices standard library, which contains the logic for handling signing in. Add following line at the top of the file

import AuthenticationServices
Then add following class to your project:
class AppleSignInView: NSObject, FlutterPlatformView { // (1)
    let frame: CGRect
    let viewId: Int64
    let args: Any?
    let window: UIWindow // (2)
    
    init(_ frame: CGRect, viewId: Int64, args: Any?, window: UIWindow) {
        self.frame = frame
        self.viewId = viewId
        self.args = args
        self.window = window
    } // (3)
    
    func view() -> UIView { // (4)
        if #available(iOS 13.0, *) { // (5)
            let button = ASAuthorizationAppleIDButton(frame: frame) // (6)
            return button
        } else {
            return UIView() // (7)
        }
    }
}
This class inherits from FlutterPlatformView (1) and contains logic for displaying an iOS view. It has 4 parameters (2):
  1. frame, which is a rectangle on the screen in which the view will be displayed. This information is passed from Flutter code
  2. viewId, a parameter that is visible in the Flutter code
  3. args, a list of arguments set on the Flutter side
  4. window, an object referring to device’s screen

In this example we’ll only use parameters number 1. and 4. Next we have a standard constructor (3) and function responsible for drawing our button (4). First we check if the user has the latest OS version. If so then we return an instance of ASAuthorizationAppleIDButton, which is a view that has Sign in with Apple button design and login (6). (You may notice that the button is initialized in one line and immediately returned in the other. This is done on purpose, you will learn why in a moment). Otherwise we return empty view (7).

Now you can add another class to your project:
class AppleSignInViewFactory: NSObject, FlutterPlatformViewFactory { // (1)
    let window: UIWindow // (2)
    
    init(window: UIWindow) {
        self.window = window
    } // (3)
    
    func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView { // (4)
        return AppleSignInView(frame, viewId: viewId, args: args, window: window) // (5)
    }
}

This class inherits from FlutterPlatformViewFactory (1) and contains logic for creating an instance of FlutterPlatformView

It has one window parameter (2), which it passes to the view it creates. Then a standard initializer (3) and create(withFrame:viewIdentifier:arguments:) method (4), which creates an instance of class we defined earlier (5).

When that’s done find application(_:launchOptions:) method and add following 2 lines of code:

let viewFactory = AppleSignInViewFactory(window: window) // (1)
registrar(forPlugin: "AnyString").register(viewFactory, withId: "AppleSignIn")//(2)

First we create an instance of the Factory class we defined earlier (1), then we register it using Flutter’s registrar (2). Notice that we pass AppleSignIn string to the register method. This must be the same string that we used in the dart code before.

That’s it, this is all the code you need to display an iOS view inside a Flutter’s widget. However, if you run this right now you won’t see anything. There is one last thing you must do.

In editor you use to write dart code find and open Info.plist file (it will be in ios folder). Then add following 2 lines to it

<key>io.flutter.embedded_views_preview</key> 
<true/>

This will allow flutter to display iOS views.

Adding Logic

Now that we have the button, we can handle the authorization process. First, we must return to the AppleSignInView class we created earlier and make it implement two interfaces. In iOS a standard way to make a class conform to an interface is by usage of extensions.

So here is the first extension:
@available(iOS 13, *) // (1)
extension AppleSignInView: ASAuthorizationControllerPresentationContextProviding { // (2)
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { // (3)
        return window // (4)
    }
}

We make this extension to only be available in iOS 13 (1), otherwise it won’t compile. Then we extend AppleSignInView class by making it implement ASAuthorizationControllerPresentationContextProviding interface (2). This interface has one function, presentationAnchor(for:) (3), which defines a screen on which the view will be drawn. We pass it the window parameter that we provided through constructor (4)

Then we add second extension:
@available(iOS 13, *) // (1)
extension AppleSignInView: ASAuthorizationControllerDelegate { // (2)
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { // (3)
        switch authorization.credential { // (4)
        case let appleIdCredential as ASAuthorizationAppleIDCredential: // (5)
            // get user data, create account if necessary and send to flutter via channel
            break
        default: // (6)
            break
        }        
    }    
}

This one is also only available in iOS 13. (1) We extend AppleSignInView by making it implement ASAuthorizationControllerDelegate (2) interface. This interface tells us whether the authorization was successful or not, with authorizationController(controller:didCompleteWithAuthorization:) method (3).

To verify that, we check the type of authorization.credential variable (4). If it is of ASAuthorizationAppleIDCredential type, then the authorization was successful. We receive data from Apple and we can use it however we want. In most cases we would send this data back to Flutter code through a channel and handle it there. Usage of Flutter method channels is beyond the scope of this post.

If authorization.credential is of different type, then the authorization either failed or was cancelled by the user.

What data do we receive in case of success? Let’s have a look.

We get all fields displayed above in the appleIdCredential variable. In the first section we get information that allows us to identify a user. You may be especially interested by user field. It, unlike the others, never changes and is a very solid way to verify your user’s identity. Values in this section are of most use to backend developers, that need to tie users signing in with Apple to their other, existing accounts.

Then we get the contact information. Just keep in mind the things about full name and email discussed before. I’d like to remind you once again, that you will only get data from this section once, when a user logs in for the first time. If you need it for later you should cache it.

In the last section we have one value, which is Apple’s guess whether this sign in was performed by a real user or not. Nice little detail, but I haven’t yet found a practical usage for it.

Now that you know what kind of data you can expect, you still must request it. You can do it first by adding code below to view() method of AppleSignInView class

button.addTarget(self, action: #selector(onSignInWithAppleTap), for: .touchUpInside)
This registers an action for a button we create. Now we must define this action. Add following method to AppleSignInView class
@objc func onSignInWithAppleTap() {
    if #available(iOS 13.0, *) {
        let appleIdProvider = ASAuthorizationAppleIDProvider()
        let request = appleIdProvider.createRequest()
        request.requestedScopes = [.fullName, .email]
        
        let authorizationController = ASAuthorizationController(authorizationRequests: [request])
        authorizationController.delegate = self
        authorizationController.presentationContextProvider = self
        authorizationController.performRequests()
    }
}

This method is pretty straight-forward. You create a provider, use it to get a request and configure the request by telling it what data you need. Then you pass it to a Controller which is displayed for the user to interact with.

And that’s it, that is all you need to do to implement Sign in with Apple in your Flutter application. Thank you for reading and I hope that you found this article useful.

App development outsourcing