Freigeben über


Verwenden der Anmeldung mit Apple in Xamarin.Forms

Anmelden mit Apple ist für alle neuen Anwendungen unter iOS 13 vorgesehen, die Authentifizierungsdienste von Drittanbietern verwenden. Die Implementierungsdetails zwischen iOS und Android unterscheiden sich ziemlich. In diesem Leitfaden wird erläutert, wie Sie dies heute tun können.Xamarin.Forms

In diesem Leitfaden und Beispiel werden bestimmte Plattformdienste verwendet, um die Anmeldung mit Apple zu verarbeiten:

  • Android mit einem generischen Webdienst, der mit Azure-Funktionen mit OpenID/OpenAuth spricht
  • iOS verwendet die native API für die Authentifizierung unter iOS 13 und greift auf einen generischen Webdienst für iOS 12 und darunter zurück.

Ein Beispiel für einen Apple-Anmeldeablauf

Dieses Beispiel bietet eine meinungsierte Implementierung, mit der Apple Sign In in Ihrer Xamarin.Forms App funktioniert.

Wir verwenden zwei Azure-Funktionen, um den Authentifizierungsfluss zu unterstützen:

  1. applesignin_auth – Generiert die Apple-Anmeldeautorisierungs-URL und leitet zu ihr um. Wir tun dies auf der Serverseite anstelle der mobilen App, sodass wir die Zwischenspeicherung und state Überprüfung vornehmen können, wenn die Server von Apple einen Rückruf senden.
  2. applesignin_callback – Verarbeitet den POST-Rückruf von Apple und austauscht den Autorisierungscode sicher für ein Zugriffstoken und ID-Token. Schließlich wird es zurück zum URI-Schema der App umgeleitet, wobei die Token in einem URL-Fragment zurückgegeben werden.

Die mobile App registriert sich selbst für die Behandlung des benutzerdefinierten URI-Schemas, das wir ausgewählt haben (in diesem Fall xamarinformsapplesignin://), damit die applesignin_callback Funktion die Token zurück an sie weiterleiten kann.

Wenn der Benutzer die Authentifizierung startet, werden die folgenden Schritte ausgeführt:

  1. Die mobile App generiert einen nonce und state einen Wert und übergibt sie an die applesignin_auth Azure-Funktion.
  2. Die applesignin_auth Azure-Funktion generiert eine Apple-Anmeldeautorisierungs-URL (unter Verwendung der bereitgestellten state und nonce), und leitet den mobilen App-Browser an ihn weiter.
  3. Der Benutzer gibt seine Anmeldeinformationen sicher auf der Apple-Anmeldeautorisierungsseite ein, die auf den Servern von Apple gehostet wird.
  4. Nachdem der Apple-Anmeldefluss auf den Apple-Servern abgeschlossen wurde, leitet Apple an die redirect_uriapplesignin_callback Azure-Funktion um.
  5. Die anforderung von Apple, die an die applesignin_callback Funktion gesendet wurde, wird überprüft, um sicherzustellen, dass die korrekten state zurückgegeben werden und dass die ID-Token-Ansprüche gültig sind.
  6. Die applesignin_callback Azure-Funktion tauscht die code von Apple gepostete Funktion für ein Zugriffstoken, ein Aktualisierungstoken und ein ID-Token aus (das Ansprüche über die Benutzer-ID, den Namen und die E-Mail enthält).
  7. Die applesignin_callback Azure-Funktion leitet schließlich zurück zum URI-Schema (xamarinformsapplesignin://) der App, das ein URI-Fragment mit den Token ansetzt (z. B. xamarinformsapplesignin://#access_token=...&refresh_token=...&id_token=...).
  8. Die mobile App analysiert das URI-Fragment in einem AppleAccount und überprüft den nonce empfangenen Anspruch mit dem nonce generierten Zufluss am Anfang des Flusses.
  9. Die mobile App ist jetzt authentifiziert!

Azure Functions

In diesem Beispiel werden Azure Functions verwendet. Alternativ kann eine ASP.NET Core Controller oder eine ähnliche Webserverlösung die gleiche Funktionalität bieten.

Konfiguration

Bei Verwendung von Azure-Funktionen müssen mehrere App-Einstellungen konfiguriert werden:

  • APPLE_SIGNIN_KEY_ID - Dies ist Ihre KeyId von früheren.
  • APPLE_SIGNIN_TEAM_ID – Dies ist in der Regel Ihre Team-ID in Ihrem Mitgliedschaftsprofil.
  • APPLE_SIGNIN_SERVER_ID: Dies ist die ServerId von früheren. Es ist nicht Ihre App-Bündel-ID, sondern der Bezeichner der von Ihnen erstellten Dienst-ID.
  • APPLE_SIGNIN_APP_CALLBACK_URI – Dies ist das benutzerdefinierte URI-Schema, mit dem Sie zurück zu Ihrer App umleiten möchten. In diesem Beispiel xamarinformsapplesignin:// wird verwendet.
  • APPLE_SIGNIN_REDIRECT_URI– Die Umleitungs-URL, die Sie beim Erstellen Ihrer Dienst-ID im Abschnitt "Apple Sign In Configuration" einrichten. Zum Testen könnte es etwa wie folgt aussehen: http://local.test:7071/api/applesignin_callback
  • APPLE_SIGNIN_P8_KEY - Der Textinhalt Ihrer .p8 Datei, wobei alle \n Newlines entfernt wurden, sodass es eine lange Zeichenfolge ist.

Sicherheitshinweise

Speichern Sie Ihren P8-Schlüssel niemals im Anwendungscode. Der Anwendungscode kann einfach heruntergeladen und zerlegt werden.

Es wird auch als schlechte Methode betrachtet, einen WebView zum Hosten des Authentifizierungsflusses zu verwenden und URL-Navigationsereignisse abzufangen, um den Autorisierungscode abzurufen. Zurzeit gibt es derzeit keine vollständig sichere Möglichkeit, die Anmeldung mit Apple auf Nicht-iOS13+-Geräten zu verarbeiten, ohne Code auf einem Server zu hosten, um den Tokenaustausch zu verarbeiten. Es wird empfohlen, den Autorisierungs-URL-Generierungscode auf einem Server zu hosten, damit Sie den Zustand zwischenspeichern und überprüfen können, wenn Apple einen POST-Rückruf auf Ihrem Server ausgibt.

Ein plattformübergreifender Anmeldedienst

Mithilfe des Xamarin.Forms DependencyService können Sie separate Authentifizierungsdienste erstellen, die die Plattformdienste unter iOS verwenden, und einen generischen Webdienst für Android und andere Nicht-iOS-Plattformen basierend auf einer freigegebenen Schnittstelle.

public interface IAppleSignInService
{
    bool Callback(string url);

    Task<AppleAccount> SignInAsync();
}

Unter iOS werden die systemeigenen APIs verwendet:

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

Die Kompilierungskennzeichnung __IOS__13 wird verwendet, um Unterstützung für iOS 13 sowie ältere Versionen bereitzustellen, die auf den generischen Webdienst zurückgreifen.

Unter Android wird der generische Webdienst mit Azure Functions verwendet:

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

Zusammenfassung

In diesem Artikel werden die Schritte beschrieben, die zum Einrichten der Anmeldung mit Apple für die Verwendung in Ihren Xamarin.Forms Anwendungen erforderlich sind.