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 :
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 lestate
serveur et le valider lorsque les serveurs d’Apple envoient un rappel.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 :
- L’application mobile génère une
nonce
valeur etstate
les transmet à laapplesignin_auth
fonction Azure. - La
applesignin_auth
fonction Azure génère une URL d’autorisation de connexion Apple (à l’aide de l’url fourniestate
etnonce
) et redirige le navigateur de l’application mobile vers celui-ci. - 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.
- Une fois le flux de connexion Apple terminé sur les serveurs Apple, Apple Redirige vers la
redirect_uri
applesignin_callback
fonction Azure. - La demande d’Apple envoyée à la
applesignin_callback
fonction est validée pour s’assurer que la valeur correctestate
est retournée et que les revendications de jeton d’ID sont valides. - La
applesignin_callback
fonction Azure échange lacode
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). - 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 exemplexamarinformsapplesignin://#access_token=...&refresh_token=...&id_token=...
). - L’application mobile analyse le fragment d’URI en un
AppleAccount
et valide lanonce
revendication reçue correspondnonce
au début du flux généré. - 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 votreKeyId
de plus tôt.APPLE_SIGNIN_TEAM_ID
- Il s’agit généralement de votre ID d’équipe trouvé dans votre profil d’appartenanceAPPLE_SIGNIN_SERVER_ID
: Il s’agit de laServerId
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 exemplexamarinformsapplesignin://
, 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.