Iniciar sessão de utilizadores com a Apple no iOS
Este documento mostra como usar a Identity Platform para adicionar a opção Iniciar sessão com a Apple à sua app iOS.
Antes de começar
Ativar a Identity Platform e ter uma app iOS básica. Para saber como ativar a Identity Platform e iniciar sessão, consulte o Início rápido.
Adira ao Apple Developer Program.
Configurar a sua app com a Apple
No site do programador da Apple:
Ative a capacidade Iniciar sessão com a Apple para a sua app.
Se usar a Identity Platform para enviar emails aos seus utilizadores, configure o seu projeto com o serviço de retransmissão de email privado da Apple usando o seguinte email:
noreply@project-id.firebaseapp.com
Também pode usar um modelo de email personalizado, se a sua app tiver um.
Agir em conformidade com os requisitos de dados anónimos da Apple
A Apple dá aos utilizadores a opção de anonimizar os respetivos dados, incluindo o endereço de email. A Apple atribui aos utilizadores que selecionam esta opção um endereço de email
ocultado com o domínio privaterelay.appleid.com
.
A sua app tem de estar em conformidade com todas as políticas ou termos de programadores aplicáveis da Apple relativamente a IDs Apple anónimos. Isto inclui obter o consentimento do utilizador antes de associar quaisquer informações de identificação pessoal (IIP) a um ID Apple anónimo. As ações que envolvem PII incluem, entre outras:
- Associar um endereço de email a um ID Apple anónimo ou vice-versa.
- Associar um número de telefone a um ID Apple anónimo ou vice-versa
- Associar uma credencial social não anónima, como o Facebook ou o Google, a um ID Apple anónimo ou vice-versa.
Para mais informações, consulte o Contrato de Licença do Programa para Programadores da Apple para a sua conta de programador da Apple.
Configurar a Apple como fornecedor
Para configurar a Apple como um Fornecedor de identidade:
Aceda à página Fornecedores de identidade na Google Cloud consola.
Clique em Adicionar um fornecedor.
Selecione Apple na lista.
Em Plataforma, selecione iOS.
Introduza o ID do pacote da sua app.
Registe os domínios da sua app clicando em Adicionar domínio em Domínios autorizados. Para fins de programação, o
localhost
já está ativado por predefinição.Em Configure a sua aplicação, clique em iOS. Copie o fragmento para o código da sua app para inicializar o SDK do cliente do Identity Platform.
Clique em Guardar.
Iniciar sessão de utilizadores com o SDK do cliente
Inicie sessão no utilizador e obtenha um token de ID através da estrutura de serviços de autenticação da Apple.
Gere uma string aleatória, conhecida como nonce, chamando
SecRandomCopyBytes(_:_:_:)
.O nonce é usado para evitar ataques de repetição. Inclui o hash SHA-256 do seu valor único no pedido de autenticação e a Apple devolve-o, sem modificações, na resposta. Em seguida, a Identity Platform valida a resposta comparando o hash original com o valor devolvido pela Apple.
Inicie o fluxo de início de sessão da Apple, incluindo o hash SHA-256 do nonce que criou no passo anterior e uma classe delegada para processar a resposta da Apple:
Swift
import CryptoKit // Unhashed nonce. fileprivate var currentNonce: String? @available(iOS 13, *) func startSignInWithAppleFlow() { let nonce = randomNonceString() currentNonce = nonce let appleIDProvider = ASAuthorizationAppleIDProvider() let request = appleIDProvider.createRequest() request.requestedScopes = [.fullName, .email] request.nonce = sha256(nonce) let authorizationController = ASAuthorizationController(authorizationRequests: [request]) authorizationController.delegate = self authorizationController.presentationContextProvider = self authorizationController.performRequests() } @available(iOS 13, *) private func sha256(_ input: String) -> String { let inputData = Data(input.utf8) let hashedData = SHA256.hash(data: inputData) let hashString = hashedData.compactMap { return String(format: "%02x", $0) }.joined() return hashString }
Objective-C
@import CommonCrypto; - (void)startSignInWithAppleFlow { NSString *nonce = [self randomNonce:32]; self.currentNonce = nonce; ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init]; ASAuthorizationAppleIDRequest *request = [appleIDProvider createRequest]; request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail]; request.nonce = [self stringBySha256HashingString:nonce]; ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]]; authorizationController.delegate = self; authorizationController.presentationContextProvider = self; [authorizationController performRequests]; } - (NSString *)stringBySha256HashingString:(NSString *)input { const char *string = [input UTF8String]; unsigned char result[CC_SHA256_DIGEST_LENGTH]; CC_SHA256(string, (CC_LONG)strlen(string), result); NSMutableString *hashed = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; for (NSInteger i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { [hashed appendFormat:@"%02x", result[i]]; } return hashed; }
Trate a resposta da Apple na sua implementação de
ASAuthorizationControllerDelegate
. Se a sessão for iniciada com êxito, use o token de ID da resposta da Apple com o nonce não hash para fazer a autenticação com a Identity Platform:Swift
@available(iOS 13.0, *) extension MainViewController: ASAuthorizationControllerDelegate { func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential { guard let nonce = currentNonce else { fatalError("Invalid state: A login callback was received, but no login request was sent.") } guard let appleIDToken = appleIDCredential.identityToken else { print("Unable to fetch identity token") return } guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else { print("Unable to serialize token string from data: \(appleIDToken.debugDescription)") return } // Initialize a Firebase credential. let credential = OAuthProvider.credential(withProviderID: "apple.com", IDToken: idTokenString, rawNonce: nonce) // Sign in with Firebase. Auth.auth().signIn(with: credential) { (authResult, error) in if error { // Error. If error.code == .MissingOrInvalidNonce, make sure // you're sending the SHA256-hashed nonce as a hex string with // your request to Apple. print(error.localizedDescription) return } // User is signed in to Firebase with Apple. // ... } } } func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { // Handle error. print("Sign in with Apple errored: \(error)") } }
Objective-C
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) { if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) { ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential; NSString *rawNonce = self.currentNonce; NSAssert(rawNonce != nil, @"Invalid state: A login callback was received, but no login request was sent."); if (appleIDCredential.identityToken == nil) { NSLog(@"Unable to fetch identity token."); return; } NSString *idToken = [[NSString alloc] initWithData:appleIDCredential.identityToken encoding:NSUTF8StringEncoding]; if (idToken == nil) { NSLog(@"Unable to serialize id token from data: %@", appleIDCredential.identityToken); } // Initialize a Firebase credential. FIROAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:@"apple.com" IDToken:idToken rawNonce:rawNonce]; // Sign in with Firebase. [[FIRAuth auth] signInWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { if (error != nil) { // Error. If error.code == FIRAuthErrorCodeMissingOrInvalidNonce, // make sure you're sending the SHA256-hashed nonce as a hex string // with your request to Apple. return; } // Sign-in succeeded! }]; } } - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)) { NSLog(@"Sign in with Apple errored: %@", error); }
Ao contrário de muitos outros fornecedores de identidade, a Apple não fornece um URL de foto.
Se um utilizador optar por não partilhar o respetivo email real com a sua app, a Apple disponibiliza
um endereço de email exclusivo para esse utilizador partilhar. Este email tem o formato
xyz@privaterelay.appleid.com
. Se configurou o serviço de retransmissão de email privado, a Apple encaminha os emails enviados para o endereço anónimo para o endereço de email real do utilizador.
A Apple só partilha informações do utilizador, como nomes a apresentar, com as apps na primeira vez que um utilizador inicia sessão. Na maioria dos casos, a Identity Platform armazena estes dados, o que lhe permite obtê-los através de firebase.auth().currentUser.displayName
durante sessões futuras. No entanto, se permitiu que os utilizadores iniciassem sessão na sua app
através da Apple antes da integração com a Identity Platform, as informações dos utilizadores
não estão disponíveis.
Eliminação da conta de utilizador
A Apple exige que as apps iOS que suportam a criação de contas também permitam que os utilizadores iniciem a eliminação da respetiva conta na app.
Quando elimina uma conta de utilizador, tem de revogar o token do utilizador antes de eliminar a conta do utilizador, bem como todos os dados que armazenou para o utilizador no Firestore, no Cloud Storage e na Firebase Realtime Database. Para mais informações, consulte o artigo Oferecer eliminação de contas na sua app na documentação de apoio técnico para programadores da Apple.
Uma vez que a Identity Platform não armazena tokens de utilizador quando os utilizadores são criados com o início de sessão da Apple, tem de pedir ao utilizador para iniciar sessão antes de revogar o respetivo token e eliminar a conta. Em alternativa, para evitar pedir ao utilizador que inicie sessão novamente, se um utilizador tiver sessão iniciada com o início de sessão da Apple, pode armazenar o código de autorização para reutilização durante a revogação do token.
Para revogar o token de um utilizador e eliminar a respetiva conta, execute o seguinte:
Swift
let user = Auth.auth().currentUser
// Check if the user has a token.
if let providerData = user?.providerData {
for provider in providerData {
guard let provider = provider as? FIRUserInfo else {
continue
}
if provider.providerID() == "apple.com" {
isAppleProviderLinked = true
}
}
}
// Re-authenticate the user and revoke their token
if isAppleProviderLinked {
let request = appleIDRequest(withState: "revokeAppleTokenAndDeleteUser")
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
} else {
// Usual user deletion
}
func authorizationController(
controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization
) {
if authorization.credential is ASAuthorizationAppleIDCredential {
let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential
if authorization.credential is ASAuthorizationAppleIDCredential {
if appleIDCredential.state == "signIn" {
// Sign in with Firebase.
// ...
} else if appleIDCredential.state == "revokeAppleTokenAndDeleteUser" {
// Revoke token with Firebase.
Auth.auth().revokeTokenWithAuthorizationCode(code) { error in
if error != nil {
// Token revocation failed.
} else {
// Token revocation succeeded then delete user again.
let user = Auth.auth().currentUser
user?.delete { error in
// ...
}
}
}
}
}
}
}
Objective-C
FIRUser *user = [FIRAuth auth].currentUser;
// Check if the user has a token.
BOOL isAppleProviderLinked = false;
for (id<FIRUserInfo> provider in user.providerData) {
if ([[provider providerID] isEqual:@"apple.com"]) {
isAppleProviderLinked = true;
}
}
// Re-authenticate the user and revoke their token
if (isAppleProviderLinked) {
if (@available(iOS 13, *)) {
ASAuthorizationAppleIDRequest *request =
[self appleIDRequestWithState:@"revokeAppleTokenAndDeleteUser"];
ASAuthorizationController *controller = [[ASAuthorizationController alloc]
initWithAuthorizationRequests:@[ request ]];
controller.delegate = self;
controller.presentationContextProvider = self;
[controller performRequests];
}
} else {
// Usual user deletion
}
- (void)authorizationController:(ASAuthorizationController *)controller
didCompleteWithAuthorization:(ASAuthorization *)authorization
API_AVAILABLE(ios(13.0)) {
if ([authorization.credential
isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
ASAuthorizationAppleIDCredential *appleIDCredential =
authorization.credential;
if ([appleIDCredential.state isEqualToString:@"signIn"]) {
// Sign in with Firebase.
// ...
} else if ([appleIDCredential.state
isEqualToString:@"revokeAppleTokenAndDeleteUser"]) {
// Revoke token with Firebase.
NSString *code =
[[NSString alloc] initWithData:appleIDCredential.authorizationCode
encoding:NSUTF8StringEncoding];
[[FIRAuth auth]
revokeTokenWithAuthorizationCode:code
completion:^(NSError *_Nullable error) {
if (error != nil) {
// Token revocation failed.
} else {
// Token revocation succeeded then delete
// user again.
FIRUser *user = [FIRAuth auth].currentUser;
[user deleteWithCompletion:^(
NSError *_Nullable error){
// ...
}];
}
}];
}
}
}
O que se segue?
- Saiba mais acerca dos utilizadores da Identity Platform.
- Permitir que os utilizadores iniciem sessão com outros fornecedores de identidade.