Inicio de sesión con Apple en Xamarin.iOS

Iniciar sesión con Apple es un nuevo servicio que proporciona protección de identidad para los usuarios de servicios de autenticación de terceros. A partir de iOS 13, Apple requiere que cualquier nueva aplicación que use servicios de autenticación de terceros también debe proporcionar inicio de sesión con Apple. Las aplicaciones existentes que se actualizan no necesitan agregar inicio de sesión con Apple hasta abril de 2020.

En este documento se presenta cómo agregar inicio de sesión con Apple a aplicaciones de iOS 13.

Configuración del desarrollador de Apple

Antes de compilar y ejecutar una aplicación con Iniciar sesión con Apple, debe completar estos pasos. En portal de certificados para desarrolladores de Apple, identificadores y perfiles:

  1. Cree un nuevo identificador de identificadores de aplicación.
  2. Escriba una descripción en el cuadro Descripción.
  3. Elija un identificador de agrupación explícito y establezca com.xamarin.AddingTheSignInWithAppleFlowToYourApp en el campo.
  4. Habilite la funcionalidad Iniciar sesión con Apple y registre la nueva identidad.
  5. Cree un nuevo perfil de aprovisionamiento con la nueva identidad.
  6. Descargue e instálelo en el dispositivo.
  7. En Visual Studio, habilite la funcionalidad Iniciar sesión con Apple en archivo Entitlements.plist.

Comprobación del estado de inicio de sesión

Cuando comience la aplicación o cuando necesite comprobar el estado de autenticación de un usuario, cree una instancia de un ASAuthorizationAppleIdProvider y compruebe el estado actual:

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

En este código, llamado durante FinishedLaunching en el AppDelegate.cs, la aplicación controlará cuándo se NotFound un estado y presentará el LoginViewController al usuario. Si el estado hubiera devuelto Authorized o Revoked, se puede presentar una acción diferente al usuario.

LoginViewController para iniciar sesión con Apple

El UIViewController que implementa la lógica de inicio de sesión y ofrece Inicio de sesión con Apple debe implementar IASAuthorizationControllerDelegate y IASAuthorizationControllerPresentationContextProviding como en el ejemplo de LoginViewController siguiente.

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

Animación de una aplicación de ejemplo con inicio de sesión con Apple

Este código de ejemplo comprueba el estado de inicio de sesión actual en PerformExistingAccountSetupFlows y se conecta a la vista actual como delegado. Si se encuentra una credencial de cadena de claves de iCloud existente o una credencial de id. de Apple, se le pedirá al usuario que lo use.

Apple proporciona ASAuthorizationAppleIdButton, un botón específicamente para este propósito. Cuando se toca, el botón desencadenará el flujo de trabajo controlado en el método HandleAuthorizationAppleIDButtonPress.

Control de la autorización

En el IASAuthorizationController implementar cualquier lógica personalizada para almacenar la cuenta del usuario. En el ejemplo siguiente se almacena la cuenta del usuario en Keychain, el propio servicio de almacenamiento de Apple.

#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

Controlador de autorización

La última parte de esta implementación es el ASAuthorizationController que administra las solicitudes de autorización para el proveedor.

#region IASAuthorizationControllerPresentation Context Providing

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

#endregion