Como fazer login dos usuários com a Apple no iOS

Este documento mostra como usar o Identity Platform para adicionar Login com a Apple ao app iOS.

Antes de começar

Como configurar seu aplicativo com a Apple

No site do desenvolvedor da Apple:

  1. Ative o recurso "Fazer login com a Apple" no seu app.

  2. Se você usa o Identity Platform para enviar e-mails aos usuários, configure o projeto com o serviço de redirecionamento de e-mail privado da Apple usando o seguinte e-mail:

    noreply@project-id.firebaseapp.com
    

    Você também pode usar um modelo de e-mail personalizado, se o app tiver um.

Conformidade com os requisitos de dados anônimos da Apple

A Apple oferece aos usuários a opção de tornar anônimos os dados, inclusive o endereço de e-mail. A Apple atribui aos usuários que selecionam essa opção um endereço de e-mail ofuscado com o domínio privaterelay.appleid.com.

Seu app precisa obedecer a quaisquer políticas ou termos de desenvolvedor aplicáveis da Apple referentes aos IDs da Apple anônimos. Isso inclui solicitar o consentimento do usuário antes de associar informações de identificação pessoal (PII, na sigla em inglês) a um ID Apple anônimo. As ações que envolvem PII incluem, entre outras:

  • Vincular um endereço de e-mail a um ID Apple anônimo ou vice-versa.
  • Vincular um número de telefone a um ID Apple anônimo ou vice-versa
  • Vincular uma credencial social não anônima, como Facebook ou Google, ao ID anônimo da Apple ou vice-versa.

Para mais informações, consulte o Contrato de Licença do Programa para desenvolvedores da Apple da sua conta de desenvolvedor da Apple.

Como configurar a Apple como um provedor

Para configurar a Apple como um provedor de identidade:

  1. Acesse a página Provedores de identidade no console do Google Cloud.

    Acessar a página "Provedores do Identity Platform"

  2. Clique em Adicionar um provedor.

  3. Selecione Apple na lista.

  4. Em Plataforma, selecione iOS.

  5. Insira o ID do pacote do seu app.

  6. Registre os domínios do seu aplicativo clicando em Adicionar domínio em Domínios autorizados. Para fins de desenvolvimento, localhost já está ativado por padrão.

  7. Em Configure seu aplicativo, clique em iOS. Copie o snippet no código do app para inicializar o SDK do cliente do Identity Platform.

  8. Clique em Salvar.

Como fazer login de usuários com o SDK do cliente

  1. Faça login do usuário e receba um token de código usando o framework de Serviços de Autenticação da Apple.

  2. Gere uma string aleatória, conhecida como nonce, chamando SecRandomCopyBytes(_:_:_:).

    O nonce é usado para evitar ataques de repetição. Você inclui o hash SHA-256 do nonce na sua solicitação de autenticação, e a Apple o retorna, não modificado, na resposta. Em seguida, o Identity Platform valida a resposta comparando o hash original com o valor retornado pela Apple.

  3. Inicie o fluxo de login da Apple, incluindo o hash SHA-256 do nonce criado na etapa 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;
    }
    
  4. Processe a resposta da Apple em sua implementação de ASAuthorizationControllerDelegate. Se o login for bem-sucedido, use o token de código da resposta da Apple com o nonce sem hash para fazer a autenticação com o 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);
    }
    

Diferentemente de muitos outros provedores de identidade, a Apple não fornece um URL de foto.

Se um usuário optar por não compartilhar seus e-mails reais com seu app, a Apple provisionará um endereço de e-mail exclusivo para ele. Este e-mail assume a forma xyz@privaterelay.appleid.com. Se você tiver configurado o serviço de retransmissão de e-mail privado, a Apple encaminhará os e-mails enviados para o endereço anônimo para o endereço de e-mail real do usuário.

A Apple só compartilha informações do usuário com os apps, como nomes de exibição, na primeira vez que um usuário faz login. Na maioria dos casos, o Identity Platform armazena esses dados, o que permite buscá-los usando firebase.auth().currentUser.displayName durante sessões futuras. No entanto, se você permitiu que os usuários fizessem login no seu aplicativo usando a Apple antes da integração com o Identity Platform, as informações do usuário não estavam disponíveis.

Exclusão da conta de usuário

A Apple exige que os apps para iOS que oferecem suporte à criação de contas também permitam que os usuários iniciem a exclusão da conta no app.

Ao excluir uma conta de usuário, é necessário revogar o token antes de excluir a conta e todos os dados armazenados no Firestore, no Cloud Storage e no Firebase Realtime Database. Para mais informações, consulte Oferecer a exclusão de contas no seu app na documentação de suporte a desenvolvedores da Apple.

Como o Identity Platform não armazena tokens de usuário quando os usuários são criados com o Login com a Apple, você precisa solicitar que o usuário faça login antes de revogar o token e excluir a conta. Como alternativa, para evitar que o usuário faça login novamente se ele estiver conectado com o recurso "Iniciar sessão com a Apple", você pode armazenar o código de autorização para reutilizar durante a revogação do token.

Para revogar o token de um usuário e excluir a conta dele, 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){
                                        // ...
                                    }];
                                  }
                                }];
    }
  }
}

A seguir