在 Xamarin 中通过 Apple 登录Sign In with Apple in Xamarin.iOS

下载示例下载示例Download Sample Download the sample

使用 Apple 登录是一项新服务,为第三方身份验证服务的用户提供标识保护。Sign In with Apple is a new service that provides identity protection for users of third-party authentication services. 从 iOS 13 开始,Apple 要求使用第三方身份验证服务的任何新应用还应提供 Apple 的登录。Beginning with iOS 13, Apple requires that any new app using a third-party authentication services should also provide Sign In with Apple. 要更新的现有应用在2020年4月之前无需添加 Apple 的登录。Existing apps being updated do not need to add Sign In with Apple until April 2020.

本文档介绍如何向 iOS 13 应用程序添加与 Apple 的登录。This document introduces how you can add Sign In with Apple to iOS 13 applications.

Apple 开发人员设置Apple developer setup

使用 Apple 的登录生成和运行应用之前,需要完成以下步骤。Before building and running an app using Sign In with Apple, you need to complete these steps. Apple 开发人员证书上,标识符 & 配置文件门户:On Apple Developer Certificates, Identifiers & Profiles portal:

  1. 创建新的应用程序 id标识符。Create a new App Ids Identifier.
  2. 在 "说明" 字段中设置说明。Set a description in the Description field.
  3. 选择显式捆绑 ID 并在字段com.xamarin.AddingTheSignInWithAppleFlowToYourApp中设置。Choose an Explicit Bundle ID and set com.xamarin.AddingTheSignInWithAppleFlowToYourApp in the field.
  4. 启用使用 Apple 功能登录并注册新标识。Enable Sign In with Apple capability and register the new Identity.
  5. 使用新标识创建新的预配配置文件。Create a new Provisioning Profile with the new Identity.
  6. 下载并将其安装在你的设备上。Download and install it on your device.
  7. 在 Visual Studio 中,启用info.plist文件中的使用 Apple 功能登录In Visual Studio, enable the Sign In with Apple capability in Entitlements.plist file.

检查登录状态Check sign in status

当你的应用程序开始运行时,或在你首次需要检查用户的身份验证状态ASAuthorizationAppleIdProvider时,实例化并检查当前状态:When your app begins, or when you first need to check the authentication status of a user, instantiate an ASAuthorizationAppleIdProvider and check the current state:

var appleIdProvider = new ASAuthorizationAppleIdProvider ();
appleIdProvider.GetCredentialState (KeychainItem.CurrentUserIdentifier, (credentialState, error) => {
    switch (credentialState) {
    case ASAuthorizationAppleIdProviderCredentialState.Authorized:
        // The Apple ID credential is valid.
        break;
    case ASAuthorizationAppleIdProviderCredentialState.Revoked:
        // The Apple ID credential is revoked.
        break;
    case ASAuthorizationAppleIdProviderCredentialState.NotFound:
        // No credential was found, so show the sign-in UI.
        InvokeOnMainThread (() => {
            var storyboard = UIStoryboard.FromName ("Main", null);

            if (!(storyboard.InstantiateViewController (nameof (LoginViewController)) is LoginViewController viewController))
                return;

            viewController.ModalPresentationStyle = UIModalPresentationStyle.FormSheet;
            viewController.ModalInPresentation = true;
            Window?.RootViewController?.PresentViewController (viewController, true, null);
        });
        break;
    }
});

FinishedLaunching此代码中, AppDelegate.cs在期间调用,应用将在LoginViewController状态为NotFound且向用户显示时处理。In this code, called during FinishedLaunching in the AppDelegate.cs, the app will handle when a state is NotFound and present the LoginViewController to the user. 如果状态返回Authorized了或Revoked,则用户可能会看到不同的操作。If the state had return Authorized or Revoked, a different action may be presented to the user.

使用 Apple 进行登录时的 LoginViewControllerA LoginViewController for Sign In with Apple

实现登录逻辑并提供与 Apple 的登录的必须实现IASAuthorizationControllerDelegateIASAuthorizationControllerPresentationContextProviding ,如下面的LoginViewController示例中所示。 UIViewControllerThe UIViewController that implements login logic and offers Sign In with Apple needs to implement IASAuthorizationControllerDelegate and IASAuthorizationControllerPresentationContextProviding as in the LoginViewController example below.

public partial class LoginViewController : UIViewController, IASAuthorizationControllerDelegate, IASAuthorizationControllerPresentationContextProviding {
    public LoginViewController (IntPtr handle) : base (handle)
    {
    }

    public override void ViewDidLoad ()
    {
        base.ViewDidLoad ();
        // Perform any additional setup after loading the view, typically from a nib.

        SetupProviderLoginView ();
    }

    public override void ViewDidAppear (bool animated)
    {
        base.ViewDidAppear (animated);

        PerformExistingAccountSetupFlows ();
    }

    void SetupProviderLoginView ()
    {
        var authorizationButton = new ASAuthorizationAppleIdButton (ASAuthorizationAppleIdButtonType.Default, ASAuthorizationAppleIdButtonStyle.White);
        authorizationButton.TouchUpInside += HandleAuthorizationAppleIDButtonPress;
        loginProviderStackView.AddArrangedSubview (authorizationButton);
    }

    // Prompts the user if an existing iCloud Keychain credential or Apple ID credential is found.
    void PerformExistingAccountSetupFlows ()
    {
        // Prepare requests for both Apple ID and password providers.
        ASAuthorizationRequest [] requests = {
            new ASAuthorizationAppleIdProvider ().CreateRequest (),
            new ASAuthorizationPasswordProvider ().CreateRequest ()
        };

        // Create an authorization controller with the given requests.
        var authorizationController = new ASAuthorizationController (requests);
        authorizationController.Delegate = this;
        authorizationController.PresentationContextProvider = this;
        authorizationController.PerformRequests ();
    }

    private void HandleAuthorizationAppleIDButtonPress (object sender, EventArgs e)
    {
        var appleIdProvider = new ASAuthorizationAppleIdProvider ();
        var request = appleIdProvider.CreateRequest ();
        request.RequestedScopes = new [] { ASAuthorizationScope.Email, ASAuthorizationScope.FullName };

        var authorizationController = new ASAuthorizationController (new [] { request });
        authorizationController.Delegate = this;
        authorizationController.PresentationContextProvider = this;
        authorizationController.PerformRequests ();
    }
}

使用 Apple 登录的示例应用的动画

此代码示例将检查中PerformExistingAccountSetupFlows的当前登录状态,并作为委托连接到当前视图。This example code checks the current login status in PerformExistingAccountSetupFlows and connects to the current view as a delegate. 如果找到现有的 iCloud 密钥链 credential 或 Apple ID 凭据,则系统会提示用户使用该凭据。If an existing iCloud Keychain credential or Apple ID credential is found, the user will be prompted to use that.

Apple 提供ASAuthorizationAppleIdButton了一个专门用于此目的的按钮。Apple provides ASAuthorizationAppleIdButton, a button specifically for this purpose. 接触后,该按钮将触发方法HandleAuthorizationAppleIDButtonPress中处理的工作流。When touched, the button will trigger the workflow handled in the method HandleAuthorizationAppleIDButtonPress.

处理授权Handling authorization

在中IASAuthorizationController ,实现任何自定义逻辑来存储用户的帐户。In the IASAuthorizationController implement any custom logic to store the user's account. 下面的示例将用户帐户存储在 Apple 自己的密钥链存储服务中。The example below stores the user's account in Keychain, Apple's own storage service.

#region IASAuthorizationController Delegate

[Export ("authorizationController:didCompleteWithAuthorization:")]
public void DidComplete (ASAuthorizationController controller, ASAuthorization authorization)
{
    if (authorization.GetCredential<ASAuthorizationAppleIdCredential> () is ASAuthorizationAppleIdCredential appleIdCredential) {
        var userIdentifier = appleIdCredential.User;
        var fullName = appleIdCredential.FullName;
        var email = appleIdCredential.Email;

        // Create an account in your system.
        // For the purpose of this demo app, store the userIdentifier in the keychain.
        try {
            new KeychainItem ("com.example.apple-samplecode.juice", "userIdentifier").SaveItem (userIdentifier);
        } catch (Exception) {
            Console.WriteLine ("Unable to save userIdentifier to keychain.");
        }

        // For the purpose of this demo app, show the Apple ID credential information in the ResultViewController.
        if (!(PresentingViewController is ResultViewController viewController))
            return;

        InvokeOnMainThread (() => {
            viewController.UserIdentifierText = userIdentifier;
            viewController.GivenNameText = fullName?.GivenName ?? "";
            viewController.FamilyNameText = fullName?.FamilyName ?? "";
            viewController.EmailText = email ?? "";

            DismissViewController (true, null);
        });
    } else if (authorization.GetCredential<ASPasswordCredential> () is ASPasswordCredential passwordCredential) {
        // Sign in using an existing iCloud Keychain credential.
        var username = passwordCredential.User;
        var password = passwordCredential.Password;

        // For the purpose of this demo app, show the password credential as an alert.
        InvokeOnMainThread (() => {
            var message = $"The app has received your selected credential from the keychain. \n\n Username: {username}\n Password: {password}";
            var alertController = UIAlertController.Create ("Keychain Credential Received", message, UIAlertControllerStyle.Alert);
            alertController.AddAction (UIAlertAction.Create ("Dismiss", UIAlertActionStyle.Cancel, null));

            PresentViewController (alertController, true, null);
        });
    }
}

[Export ("authorizationController:didCompleteWithError:")]
public void DidComplete (ASAuthorizationController controller, NSError error)
{
    Console.WriteLine (error);
}

#endregion

授权控制器Authorization Controller

此实现中的最后一个部分是ASAuthorizationController ,它管理提供程序的授权请求。The final piece in this implementation is the ASAuthorizationController which manages authorization requests for the provider.

#region IASAuthorizationControllerPresentation Context Providing

public UIWindow GetPresentationAnchor (ASAuthorizationController controller) => View.Window;

#endregion