Condividi tramite


Usare Accedi con Apple in Xamarin.Forms

L'accesso con Apple è destinato a tutte le nuove applicazioni in iOS 13 che usano servizi di autenticazione di terze parti. I dettagli di implementazione tra iOS e Android sono molto diversi. Questa guida illustra come eseguire questa operazione oggi in Xamarin.Forms.

In questa guida ed esempio vengono usati servizi di piattaforma specifici per gestire l'accesso con Apple:

  • Android che usa un servizio Web generico che comunica con Funzioni di Azure con OpenID/OpenAuth
  • iOS usa l'API nativa per l'autenticazione in iOS 13 ed esegue il fallback a un servizio Web generico per iOS 12 e versioni successive

Flusso di accesso Apple di esempio

Questo esempio offre un'implementazione opinioneata per il funzionamento dell'accesso di Apple nell'app Xamarin.Forms .

Per facilitare il flusso di autenticazione si usano due Funzioni di Azure:

  1. applesignin_auth : genera l'URL di autorizzazione di accesso Apple e lo reindirizza. Questa operazione viene eseguita sul lato server, anziché sull'app per dispositivi mobili, in modo da poterla memorizzare nella state cache e convalidarla quando i server apple inviano un callback.
  2. applesignin_callback - Gestisce il callback POST da Apple e scambia in modo sicuro il codice di autorizzazione per un token di accesso e un token ID. Infine, reindirizza nuovamente allo schema URI dell'app, passando i token in un frammento di URL.

L'app per dispositivi mobili si registra per gestire lo schema URI personalizzato selezionato (in questo caso xamarinformsapplesignin://) in modo che la applesignin_callback funzione possa inoltrare nuovamente i token.

Quando l'utente avvia l'autenticazione, vengono eseguiti i passaggi seguenti:

  1. L'app per dispositivi mobili genera un nonce valore e state e li passa alla applesignin_auth funzione di Azure.
  2. La applesignin_auth funzione di Azure genera un URL di autorizzazione di accesso Apple (usando il state specificato e nonce) e reindirizza il browser dell'app per dispositivi mobili.
  3. L'utente immette le credenziali in modo sicuro nella pagina di autorizzazione di accesso Apple ospitata nei server Apple.
  4. Al termine del flusso di accesso Apple nei server Apple, Apple Redirects to the redirect_uri che sarà la applesignin_callback funzione di Azure.
  5. La richiesta da Apple inviata alla applesignin_callback funzione viene convalidata per assicurarsi che venga restituita la corretta state e che le attestazioni del token ID siano valide.
  6. La applesignin_callback funzione di Azure scambia l'oggetto code inviato da Apple per un token di accesso, un token di aggiornamento e un token ID (che contiene attestazioni relative all'ID utente, al nome e alla posta elettronica).
  7. La applesignin_callback funzione di Azure reindirizza infine di nuovo allo schema URI dell'app (xamarinformsapplesignin://) aggiungendo un frammento URI con i token ( ad esempio xamarinformsapplesignin://#access_token=...&refresh_token=...&id_token=...).
  8. L'app per dispositivi mobili analizza il frammento URI in un AppleAccount oggetto e convalida che l'attestazione nonce ricevuta corrisponda all'oggetto nonce generato all'inizio del flusso.
  9. L'app per dispositivi mobili è ora autenticata.

Funzioni di Azure

Questo esempio usa Funzioni di Azure. In alternativa, una soluzione ASP.NET Core Controller o server Web simile potrebbe offrire le stesse funzionalità.

Impostazione

È necessario configurare diverse impostazioni dell'app quando si usa Funzioni di Azure:

  • APPLE_SIGNIN_KEY_ID - Questo è il tuo KeyId da prima.
  • APPLE_SIGNIN_TEAM_ID- Questo è in genere l'ID team trovato nel profilo di appartenenza
  • APPLE_SIGNIN_SERVER_ID: si tratta dell'oggetto ServerId precedente. Non è l'ID bundle dell'app, ma piuttosto l'identificatore dell'ID dei servizi creato.
  • APPLE_SIGNIN_APP_CALLBACK_URI - Si tratta dello schema URI personalizzato con cui si vuole eseguire il reindirizzamento all'app. In questo esempio xamarinformsapplesignin:// viene usato .
  • APPLE_SIGNIN_REDIRECT_URI- URL di reindirizzamento configurato durante la creazione dell'ID dei servizi nella sezione Configurazione dell'accesso Apple. Per eseguire il test, potrebbe essere simile al seguente: http://local.test:7071/api/applesignin_callback
  • APPLE_SIGNIN_P8_KEY - Il contenuto del testo del .p8 file, con tutte le \n nuove righe rimosse in modo che sia una stringa lunga

Considerazioni sulla sicurezza

Non archiviare mai la chiave P8 all'interno del codice dell'applicazione. Il codice dell'applicazione è facile da scaricare e disassemblare.

È anche considerata una procedura non valida da usare WebView per ospitare il flusso di autenticazione e per intercettare gli eventi di spostamento URL per ottenere il codice di autorizzazione. Attualmente non esiste un modo completamente sicuro per gestire l'accesso con Apple in dispositivi non iOS13+ senza ospitare codice in un server per gestire lo scambio di token. È consigliabile ospitare il codice di generazione dell'URL di autorizzazione in un server in modo da poter memorizzare nella cache lo stato e convalidarlo quando Apple rilascia un callback POST al server.

Un servizio di accesso multipiattaforma

Xamarin.Forms Usando DependencyService, è possibile creare servizi di autenticazione separati che usano i servizi della piattaforma in iOS e un servizio Web generico per Android e altre piattaforme non iOS basate su un'interfaccia condivisa.

public interface IAppleSignInService
{
    bool Callback(string url);

    Task<AppleAccount> SignInAsync();
}

In iOS vengono usate le API native:

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

Il flag __IOS__13 di compilazione viene usato per fornire supporto per iOS 13 e versioni legacy che esedono al servizio Web generico.

In Android viene usato il servizio Web generico con Funzioni di Azure:

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

Riepilogo

Questo articolo descrive i passaggi necessari per configurare l'accesso con Apple per l'uso nelle Xamarin.Forms applicazioni.