Partager via


Utiliser la connexion avec Apple dans Xamarin.Forms

Connectez-vous avec Apple pour toutes les nouvelles applications sur iOS 13 qui utilisent des services d’authentification tiers. Les détails de l’implémentation entre iOS et Android sont très différents. Ce guide explique comment procéder aujourd’hui en Xamarin.Forms.

Dans ce guide et cet exemple, des services de plateforme spécifiques sont utilisés pour gérer la connexion avec Apple :

  • Android utilisant un service web générique parlant avec Azure Functions avec OpenID/OpenAuth
  • iOS utilise l’API native pour l’authentification sur iOS 13 et revient à un service web générique pour iOS 12 et versions ultérieures

Un exemple de flux de connexion Apple

Cet exemple propose une implémentation avisée pour permettre à Apple Sign In de fonctionner dans votre Xamarin.Forms application.

Nous utilisons deux fonctions Azure pour faciliter le flux d’authentification :

  1. applesignin_auth - Génère l’URL d’autorisation de connexion Apple et la redirige vers celle-ci. Nous le faisons côté serveur, au lieu de l’application mobile, afin de pouvoir mettre en cache le state serveur et le valider lorsque les serveurs d’Apple envoient un rappel.
  2. applesignin_callback - Gère le rappel POST d’Apple et échange en toute sécurité le code d’autorisation pour un jeton d’accès et un jeton d’ID. Enfin, il est redirigé vers le schéma d’URI de l’application, en passant les jetons dans un fragment d’URL.

L’application mobile s’inscrit pour gérer le schéma d’URI personnalisé que nous avons sélectionné (dans ce cas xamarinformsapplesignin://) afin que la applesignin_callback fonction puisse retranscrire les jetons.

Lorsque l’utilisateur démarre l’authentification, les étapes suivantes se produisent :

  1. L’application mobile génère une nonce valeur et state les transmet à la applesignin_auth fonction Azure.
  2. La applesignin_auth fonction Azure génère une URL d’autorisation de connexion Apple (à l’aide de l’url fournie state et nonce) et redirige le navigateur de l’application mobile vers celui-ci.
  3. L’utilisateur entre ses informations d’identification en toute sécurité dans la page d’autorisation de connexion Apple hébergée sur les serveurs d’Apple.
  4. Une fois le flux de connexion Apple terminé sur les serveurs Apple, Apple Redirige vers la redirect_uri applesignin_callback fonction Azure.
  5. La demande d’Apple envoyée à la applesignin_callback fonction est validée pour s’assurer que la valeur correcte state est retournée et que les revendications de jeton d’ID sont valides.
  6. La applesignin_callback fonction Azure échange la code publication par Apple, pour un jeton d’accès, un jeton d’actualisation et un jeton d’ID (qui contient des revendications sur l’ID d’utilisateur, le nom et l’e-mail).
  7. La applesignin_callback fonction Azure redirige enfin vers le schéma d’URI de l’application (xamarinformsapplesignin://) en ajoutant un fragment d’URI avec les jetons (par exemple xamarinformsapplesignin://#access_token=...&refresh_token=...&id_token=...).
  8. L’application mobile analyse le fragment d’URI en un AppleAccount et valide la nonce revendication reçue correspond nonce au début du flux généré.
  9. L’application mobile est désormais authentifiée !

Azure Functions

Cet exemple utilise Azure Functions. Vous pouvez également fournir une solution de contrôleur principal ASP.NET ou de serveur web similaire.

Configuration

Plusieurs paramètres d’application doivent être configurés lors de l’utilisation d’Azure Functions :

  • APPLE_SIGNIN_KEY_ID - C’est votre KeyId de plus tôt.
  • APPLE_SIGNIN_TEAM_ID - Il s’agit généralement de votre ID d’équipe trouvé dans votre profil d’appartenance
  • APPLE_SIGNIN_SERVER_ID: Il s’agit de la ServerId version antérieure. Il ne s’agit pas de votre ID d’offre groupée d’applications, mais plutôt de l’identificateur de l’ID de services que vous avez créé.
  • APPLE_SIGNIN_APP_CALLBACK_URI - Il s’agit du schéma d’URI personnalisé avec lequel vous souhaitez vous rediriger vers votre application. Dans cet exemple xamarinformsapplesignin:// , il est utilisé.
  • APPLE_SIGNIN_REDIRECT_URI- URL de redirection que vous configurez lors de la création de votre ID de services dans la section Configuration de connexion Apple. Pour tester, il peut ressembler à ceci : http://local.test:7071/api/applesignin_callback
  • APPLE_SIGNIN_P8_KEY - Le contenu texte de votre .p8 fichier, avec toutes les \n lignes de nouvelle ligne supprimées de sorte qu’il s’agit d’une chaîne longue

Considérations de sécurité

Ne stockez jamais votre clé P8 à l’intérieur de votre code d’application. Le code d’application est facile à télécharger et désassembler.

Il est également considéré comme une mauvaise pratique d’utiliser un WebView pour héberger le flux d’authentification et d’intercepter les événements de navigation d’URL pour obtenir le code d’autorisation. À ce stade, il n’existe actuellement aucun moyen entièrement sécurisé de gérer la connexion avec Apple sur les appareils non iOS13+ sans héberger de code sur un serveur pour gérer l’échange de jetons. Nous vous recommandons d’héberger le code de génération d’URL d’autorisation sur un serveur afin de pouvoir mettre en cache l’état et le valider quand Apple émet un rappel POST sur votre serveur.

Un service de connexion multiplateforme

À l’aide de Xamarin.Forms DependencyService, vous pouvez créer des services d’authentification distincts qui utilisent les services de plateforme sur iOS et un service web générique pour Android et d’autres plateformes non iOS basées sur une interface partagée.

public interface IAppleSignInService
{
    bool Callback(string url);

    Task<AppleAccount> SignInAsync();
}

Sur iOS, les API natives sont utilisées :

public class AppleSignInServiceiOS : IAppleSignInService
{
#if __IOS__13
    AuthManager authManager;
#endif

    bool Is13 => UIDevice.CurrentDevice.CheckSystemVersion(13, 0);
    WebAppleSignInService webSignInService;

    public AppleSignInServiceiOS()
    {
        if (!Is13)
            webSignInService = new WebAppleSignInService();
    }

    public async Task<AppleAccount> SignInAsync()
    {
        // Fallback to web for older iOS versions
        if (!Is13)
            return await webSignInService.SignInAsync();

        AppleAccount appleAccount = default;

#if __IOS__13
        var provider = new ASAuthorizationAppleIdProvider();
        var req = provider.CreateRequest();

        authManager = new AuthManager(UIApplication.SharedApplication.KeyWindow);

        req.RequestedScopes = new[] { ASAuthorizationScope.FullName, ASAuthorizationScope.Email };
        var controller = new ASAuthorizationController(new[] { req });

        controller.Delegate = authManager;
        controller.PresentationContextProvider = authManager;

        controller.PerformRequests();

        var creds = await authManager.Credentials;

        if (creds == null)
            return null;

        appleAccount = new AppleAccount();
        appleAccount.IdToken = JwtToken.Decode(new NSString(creds.IdentityToken, NSStringEncoding.UTF8).ToString());
        appleAccount.Email = creds.Email;
        appleAccount.UserId = creds.User;
        appleAccount.Name = NSPersonNameComponentsFormatter.GetLocalizedString(creds.FullName, NSPersonNameComponentsFormatterStyle.Default, NSPersonNameComponentsFormatterOptions.Phonetic);
        appleAccount.RealUserStatus = creds.RealUserStatus.ToString();
#endif

        return appleAccount;
    }

    public bool Callback(string url) => true;
}

#if __IOS__13
class AuthManager : NSObject, IASAuthorizationControllerDelegate, IASAuthorizationControllerPresentationContextProviding
{
    public Task<ASAuthorizationAppleIdCredential> Credentials
        => tcsCredential?.Task;

    TaskCompletionSource<ASAuthorizationAppleIdCredential> tcsCredential;

    UIWindow presentingAnchor;

    public AuthManager(UIWindow presentingWindow)
    {
        tcsCredential = new TaskCompletionSource<ASAuthorizationAppleIdCredential>();
        presentingAnchor = presentingWindow;
    }

    public UIWindow GetPresentationAnchor(ASAuthorizationController controller)
        => presentingAnchor;

    [Export("authorizationController:didCompleteWithAuthorization:")]
    public void DidComplete(ASAuthorizationController controller, ASAuthorization authorization)
    {
        var creds = authorization.GetCredential<ASAuthorizationAppleIdCredential>();
        tcsCredential?.TrySetResult(creds);
    }

    [Export("authorizationController:didCompleteWithError:")]
    public void DidComplete(ASAuthorizationController controller, NSError error)
        => tcsCredential?.TrySetException(new Exception(error.LocalizedDescription));
}
#endif

L’indicateur __IOS__13 de compilation est utilisé pour prendre en charge iOS 13, ainsi que les versions héritées qui revient au service web générique.

Sur Android, le service web générique avec Azure Functions est utilisé :

public class WebAppleSignInService : IAppleSignInService
{
    // IMPORTANT: This is what you register each native platform's url handler to be
    public const string CallbackUriScheme = "xamarinformsapplesignin";
    public const string InitialAuthUrl = "http://local.test:7071/api/applesignin_auth";

    string currentState;
    string currentNonce;

    TaskCompletionSource<AppleAccount> tcsAccount = null;

    public bool Callback(string url)
    {
        // Only handle the url with our callback uri scheme
        if (!url.StartsWith(CallbackUriScheme + "://"))
            return false;

        // Ensure we have a task waiting
        if (tcsAccount != null && !tcsAccount.Task.IsCompleted)
        {
            try
            {
                // Parse the account from the url the app opened with
                var account = AppleAccount.FromUrl(url);

                // IMPORTANT: Validate the nonce returned is the same as our originating request!!
                if (!account.IdToken.Nonce.Equals(currentNonce))
                    tcsAccount.TrySetException(new InvalidOperationException("Invalid or non-matching nonce returned"));

                // Set our account result
                tcsAccount.TrySetResult(account);
            }
            catch (Exception ex)
            {
                tcsAccount.TrySetException(ex);
            }
        }

        tcsAccount.TrySetResult(null);
        return false;
    }

    public async Task<AppleAccount> SignInAsync()
    {
        tcsAccount = new TaskCompletionSource<AppleAccount>();

        // Generate state and nonce which the server will use to initial the auth
        // with Apple.  The nonce should flow all the way back to us when our function
        // redirects to our app
        currentState = Util.GenerateState();
        currentNonce = Util.GenerateNonce();

        // Start the auth request on our function (which will redirect to apple)
        // inside a browser (either SFSafariViewController, Chrome Custom Tabs, or native browser)
        await Xamarin.Essentials.Browser.OpenAsync($"{InitialAuthUrl}?&state={currentState}&nonce={currentNonce}",
            Xamarin.Essentials.BrowserLaunchMode.SystemPreferred);

        return await tcsAccount.Task;
    }
}

Résumé

Cet article décrit les étapes nécessaires à la configuration de la connexion avec Apple pour une utilisation dans vos Xamarin.Forms applications.