Utilizar o início de sessão com a Apple in Xamarin.Forms
Entrar com a Apple é para todos os novos aplicativos no iOS 13 que usam serviços de autenticação de terceiros. Os detalhes de implementação entre iOS e Android são bem diferentes. Este guia explica como você pode fazer isso hoje no Xamarin.Forms.
Neste guia e exemplo, serviços de plataforma específicos são usados para lidar com o login com a Apple:
- Android usando um serviço Web genérico conversando com o Azure Functions com OpenID/OpenAuth
- O iOS usa a API nativa para autenticação no iOS 13 e retorna para um serviço Web genérico para iOS 12 e versões inferiores
Um exemplo de fluxo de login da Apple
Este exemplo oferece uma implementação opinativa para fazer com que o Apple Sign In funcione em seu Xamarin.Forms aplicativo.
Usamos duas Funções do Azure para ajudar com o fluxo de autenticação:
applesignin_auth
- Gera o URL de autorização de login da Apple e redireciona para ele. Fazemos isso no lado do servidor, em vez do aplicativo móvel, para que possamos armazenar em cache estate
validá-lo quando os servidores da Apple enviarem um retorno de chamada.applesignin_callback
- Manipula o retorno de chamada POST da Apple e troca com segurança o código de autorização por um Access Token e ID Token. Finalmente, ele redireciona de volta para o Esquema de URI do aplicativo, passando de volta os tokens em um fragmento de URL.
O aplicativo móvel se registra para lidar com o esquema de URI personalizado que selecionamos (neste caso xamarinformsapplesignin://
) para que a applesignin_callback
função possa retransmitir os tokens de volta para ele.
Quando o usuário inicia a autenticação, as seguintes etapas acontecem:
- O aplicativo móvel gera um
nonce
valor estate
e os passa para aapplesignin_auth
função do Azure. - A
applesignin_auth
função do Azure gera uma URL de Autorização de Entrada da Apple (usando o fornecidostate
enonce
) e redireciona o navegador do aplicativo móvel para ele. - O usuário insere suas credenciais com segurança na página de autorização de login da Apple hospedada nos servidores da Apple.
- Depois que o fluxo de login da Apple terminar nos servidores da Apple, a Apple redireciona para o
redirect_uri
que será aapplesignin_callback
função do Azure. - A solicitação da Apple enviada para a
applesignin_callback
função é validada para garantir que o corretostate
seja retornado e que as declarações de ID Token sejam válidas. - A
applesignin_callback
função do Azure troca o postadocode
pela Apple por um Token de Acesso, Token de Atualização e Token de ID (que contém declarações sobre a ID do Usuário, o Nome e o Email). - A
applesignin_callback
função do Azure finalmente redireciona de volta para o esquema de URI do aplicativo (xamarinformsapplesignin://
) anexando um fragmento de URI com os Tokens (por exemplo,xamarinformsapplesignin://#access_token=...&refresh_token=...&id_token=...
). - O aplicativo móvel analisa o Fragmento de URI em um
AppleAccount
e valida anonce
declaração recebida corresponde ànonce
gerada no início do fluxo. - O aplicativo móvel agora está autenticado!
Azure Functions
Este exemplo usa o Azure Functions. Como alternativa, um ASP.NET Core Controller ou uma solução de servidor Web semelhante pode fornecer a mesma funcionalidade.
Configuração
Várias configurações de aplicativo precisam ser definidas ao usar o Azure Functions:
APPLE_SIGNIN_KEY_ID
- Este é o seuKeyId
de antes.APPLE_SIGNIN_TEAM_ID
- Este é geralmente o seu ID de Equipe encontrado em seu Perfil de MembroAPPLE_SIGNIN_SERVER_ID
: Este é oServerId
de antes. Não é o ID do Pacote de Aplicativos, mas sim o Identificador do ID de Serviços que você criou.APPLE_SIGNIN_APP_CALLBACK_URI
- Este é o esquema de URI personalizado com o qual você deseja redirecionar de volta para seu aplicativo. Neste exemploxamarinformsapplesignin://
é usado.APPLE_SIGNIN_REDIRECT_URI
- O URL de redirecionamento que você configura ao criar seu ID de Serviços na seção Configuração de Login da Apple. Para testar, pode ser algo como:http://local.test:7071/api/applesignin_callback
APPLE_SIGNIN_P8_KEY
- O conteúdo de texto do seu.p8
arquivo, com todas as\n
novas linhas removidas para que seja uma cadeia de caracteres longa
Considerações de segurança
Nunca armazene sua chave P8 dentro do código do aplicativo. O código do aplicativo é fácil de baixar e desmontar.
Também é considerada uma prática ruim usar um para hospedar o fluxo de WebView
autenticação e interceptar eventos de navegação de URL para obter o código de autorização. No momento, não há nenhuma maneira totalmente segura de lidar com o login com a Apple em dispositivos que não sejam iOS13+ sem hospedar algum código em um servidor para lidar com a troca de tokens. Recomendamos hospedar o código de geração de URL de autorização em um servidor para que você possa armazenar em cache o estado e validá-lo quando a Apple emitir um retorno de chamada POST para o servidor.
Um serviço de login entre plataformas
Usando o Xamarin.Forms DependencyService, você pode criar serviços de autenticação separados que usam os serviços de plataforma no iOS e um serviço Web genérico para Android e outras plataformas não iOS com base em uma interface compartilhada.
public interface IAppleSignInService
{
bool Callback(string url);
Task<AppleAccount> SignInAsync();
}
No iOS, as APIs nativas são usadas:
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
O sinalizador __IOS__13
de compilação é usado para fornecer suporte para o iOS 13, bem como versões herdadas que fallback para o serviço Web genérico.
No Android, o serviço Web genérico com o Azure Functions é usado:
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;
}
}
Resumo
Este artigo descreveu os passos necessários para configurar o Iniciar sessão com a Apple para utilização nas suas Xamarin.Forms aplicações.