在 Xamarin.iOS 中使用 Apple 登录

Download Sample下载示例

使用 Apple 登录是一项新服务,为第三方身份验证服务的用户提供标识保护。 从 iOS 13 开始,Apple 要求使用第三方身份验证服务的任何新应用也应提供 Apple 登录。 在 2020 年 4 月之前,更新的现有应用无需添加 Apple 登录。

本文档介绍了如何将使用 Apple 登录添加到 iOS 13 应用程序。

Apple 开发人员设置

在使用 Apple 登录生成和运行应用之前,需要完成这些步骤。 在 Apple 开发人员证书、标识符和配置文件门户上:

  1. 创建新的应用 ID 标识符。
  2. 在“说明”字段中设置说明。
  3. 选择显式 捆绑 ID 并在字段中设置 com.xamarin.AddingTheSignInWithAppleFlowToYourApp
  4. 启用“使用 Apple 登录”功能并注册新标识。
  5. 使用新的标识创建新的预配配置文件。
  6. 在设备上下载并安装它。
  7. 在 Visual Studio 中,在 Entitlements.plist 文件中启用使用 Apple 登录功能。

检查登录状态

应用启动时,或首次需要检查用户的身份验证状态时,实例化 ASAuthorizationAppleIdProvider 并检查当前状态:

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

在此代码中,在 AppDelegate.csFinishedLaunching期间调用,应用将在状态 NotFound 时处理,并将 LoginViewController 呈现给用户。 如果状态已返回 AuthorizedRevoked,则可能会向用户显示其他操作。

使用 Apple 登录的 LoginViewController

实现登录逻辑并提供使用 Apple 登录的 UIViewController 需要实现 IASAuthorizationControllerDelegateIASAuthorizationControllerPresentationContextProviding,如以下示例中的 LoginViewController 所示。

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

Animation of sample app using Sign In with Apple

此示例代码检查 PerformExistingAccountSetupFlows 中的当前登录状态,并作为委托连接到当前视图。 如果找到现有的 iCloud 密钥链凭据或 Apple ID 凭据,系统会提示用户使用该凭据。

Apple 提供 ASAuthorizationAppleIdButton,这是一个专门用于此目的的按钮。 触摸时,该按钮将触发方法 HandleAuthorizationAppleIDButtonPress 中处理的工作流。

处理授权

IASAuthorizationController 实现任何自定义逻辑来存储用户帐户。 以下示例将用户帐户存储在 Apple 自己的存储服务 Keychain 中。

#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

授权控制器

此实现的最后一部分是管理提供程序的授权请求的 ASAuthorizationController

#region IASAuthorizationControllerPresentation Context Providing

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

#endregion