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:
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 undstate
Überprüfung vornehmen können, wenn die Server von Apple einen Rückruf senden.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:
- Die mobile App generiert einen
nonce
undstate
einen Wert und übergibt sie an dieapplesignin_auth
Azure-Funktion. - Die
applesignin_auth
Azure-Funktion generiert eine Apple-Anmeldeautorisierungs-URL (unter Verwendung der bereitgestelltenstate
undnonce
), und leitet den mobilen App-Browser an ihn weiter. - Der Benutzer gibt seine Anmeldeinformationen sicher auf der Apple-Anmeldeautorisierungsseite ein, die auf den Servern von Apple gehostet wird.
- Nachdem der Apple-Anmeldefluss auf den Apple-Servern abgeschlossen wurde, leitet Apple an die
redirect_uri
applesignin_callback
Azure-Funktion um. - Die anforderung von Apple, die an die
applesignin_callback
Funktion gesendet wurde, wird überprüft, um sicherzustellen, dass die korrektenstate
zurückgegeben werden und dass die ID-Token-Ansprüche gültig sind. - Die
applesignin_callback
Azure-Funktion tauscht diecode
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). - 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=...
). - Die mobile App analysiert das URI-Fragment in einem
AppleAccount
und überprüft dennonce
empfangenen Anspruch mit demnonce
generierten Zufluss am Anfang des Flusses. - 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 IhreKeyId
von früheren.APPLE_SIGNIN_TEAM_ID
– Dies ist in der Regel Ihre Team-ID in Ihrem Mitgliedschaftsprofil.APPLE_SIGNIN_SERVER_ID
: Dies ist dieServerId
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 Beispielxamarinformsapplesignin://
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.