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:
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 nellastate
cache e convalidarla quando i server apple inviano un callback.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:
- L'app per dispositivi mobili genera un
nonce
valore estate
e li passa allaapplesignin_auth
funzione di Azure. - La
applesignin_auth
funzione di Azure genera un URL di autorizzazione di accesso Apple (usando ilstate
specificato enonce
) e reindirizza il browser dell'app per dispositivi mobili. - L'utente immette le credenziali in modo sicuro nella pagina di autorizzazione di accesso Apple ospitata nei server Apple.
- Al termine del flusso di accesso Apple nei server Apple, Apple Redirects to the
redirect_uri
che sarà laapplesignin_callback
funzione di Azure. - La richiesta da Apple inviata alla
applesignin_callback
funzione viene convalidata per assicurarsi che venga restituita la correttastate
e che le attestazioni del token ID siano valide. - La
applesignin_callback
funzione di Azure scambia l'oggettocode
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). - La
applesignin_callback
funzione di Azure reindirizza infine di nuovo allo schema URI dell'app (xamarinformsapplesignin://
) aggiungendo un frammento URI con i token ( ad esempioxamarinformsapplesignin://#access_token=...&refresh_token=...&id_token=...
). - L'app per dispositivi mobili analizza il frammento URI in un
AppleAccount
oggetto e convalida che l'attestazionenonce
ricevuta corrisponda all'oggettononce
generato all'inizio del flusso. - 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 tuoKeyId
da prima.APPLE_SIGNIN_TEAM_ID
- Questo è in genere l'ID team trovato nel profilo di appartenenzaAPPLE_SIGNIN_SERVER_ID
: si tratta dell'oggettoServerId
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 esempioxamarinformsapplesignin://
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.