Autenticar usando Apple

Você pode permitir que seus usuários se autentiquem com o Firebase usando o ID Apple usando o SDK do Firebase para realizar o fluxo de login do OAuth 2.0 de ponta a ponta.

Antes de você começar

Para fazer login de usuários que usam a Apple, primeiro configure Sign In with Apple no site do desenvolvedor da Apple e, em seguida, ative a Apple como provedor de login para seu projeto do Firebase.

Participe do Programa de Desenvolvedores Apple

O Sign In with Apple só pode ser configurado por membros do Apple Developer Program .

Configurar login com Apple

  1. Habilite Sign In with Apple para seu aplicativo na página Certificados, identificadores e perfis do site do desenvolvedor da Apple.
  2. Associe seu site ao seu aplicativo conforme descrito na primeira seção de Configurar login com a Apple para a web . Quando solicitado, registre o seguinte URL como URL de retorno:
    https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
    Você pode obter o ID do projeto do Firebase na página de configurações do console do Firebase . Quando terminar, anote seu novo ID de serviço, que você precisará na próxima seção.
  3. Crie uma chave privada de login com Apple . Você precisará de sua nova chave privada e ID de chave na próxima seção.
  4. Se você usar algum dos recursos do Firebase Authentication que enviam e-mails aos usuários, incluindo login com link de e-mail, verificação de endereço de e-mail, revogação de alteração de conta e outros, configure o serviço de retransmissão de e-mail privado da Apple e registre-se noreply@ YOUR_FIREBASE_PROJECT_ID .firebaseapp.com (ou seu domínio de modelo de e-mail personalizado) para que a Apple possa retransmitir e-mails enviados pelo Firebase Authentication para endereços de e-mail anônimos da Apple.

Habilite a Apple como provedor de login

  1. Adicione o Firebase ao seu projeto Apple . Não se esqueça de registrar o ID do pacote do seu aplicativo ao configurá-lo no Firebase console.
  2. No console do Firebase , abra a seção Auth . Na guia Método de login , habilite o provedor Apple . Especifique o ID do serviço que você criou na seção anterior. Além disso, na seção de configuração do fluxo de código OAuth , especifique seu Apple Team ID e a chave privada e o ID de chave que você criou na seção anterior.

Cumpra os requisitos de dados anonimizados da Apple

Sign In with Apple oferece aos usuários a opção de anonimizar seus dados, incluindo seu endereço de e-mail, ao fazer login. Os usuários que escolhem esta opção possuem endereços de e-mail com o domínio privaterelay.appleid.com . Ao usar o Sign In with Apple em seu aplicativo, você deve cumprir quaisquer políticas ou termos de desenvolvedor aplicáveis ​​da Apple em relação a esses IDs Apple anônimos.

Isso inclui obter qualquer consentimento necessário do usuário antes de associar qualquer informação pessoal de identificação direta a um ID Apple anônimo. Ao usar o Firebase Authentication, isso pode incluir as seguintes ações:

  • Vincule um endereço de e-mail a um ID Apple anônimo ou vice-versa.
  • Vincule um número de telefone a um ID Apple anônimo ou vice-versa
  • Vincule uma credencial social não anônima (Facebook, Google, etc.) a um ID Apple anônimo ou vice-versa.

A lista acima não é exaustiva. Consulte o Contrato de licença do Apple Developer Program na seção Assinatura da sua conta de desenvolvedor para garantir que seu aplicativo atenda aos requisitos da Apple.

Faça login na Apple e autentique-se no Firebase

Para autenticar com uma conta Apple, primeiro faça login do usuário em sua conta Apple usando a estrutura AuthenticationServices da Apple e, em seguida, use o token de ID da resposta da Apple para criar um objeto Firebase AuthCredential :

  1. Para cada solicitação de login, gere uma string aleatória — um "nonce" — que você usará para garantir que o token de ID obtido foi concedido especificamente em resposta à solicitação de autenticação do seu aplicativo. Esta etapa é importante para evitar ataques de repetição.

    Você pode gerar um nonce criptograficamente seguro com SecRandomCopyBytes(_:_:_) , como no exemplo a seguir:

    Rápido

    private func randomNonceString(length: Int = 32) -> String {
      precondition(length > 0)
      var randomBytes = [UInt8](repeating: 0, count: length)
      let errorCode = SecRandomCopyBytes(kSecRandomDefault, randomBytes.count, &randomBytes)
      if errorCode != errSecSuccess {
        fatalError(
          "Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)"
        )
      }
    
      let charset: [Character] =
        Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
    
      let nonce = randomBytes.map { byte in
        // Pick a random character from the set, wrapping around if needed.
        charset[Int(byte) % charset.count]
      }
    
      return String(nonce)
    }
    
        

    Objetivo-C

    // Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce
    - (NSString *)randomNonce:(NSInteger)length {
      NSAssert(length > 0, @"Expected nonce to have positive length");
      NSString *characterSet = @"0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._";
      NSMutableString *result = [NSMutableString string];
      NSInteger remainingLength = length;
    
      while (remainingLength > 0) {
        NSMutableArray *randoms = [NSMutableArray arrayWithCapacity:16];
        for (NSInteger i = 0; i < 16; i++) {
          uint8_t random = 0;
          int errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random);
          NSAssert(errorCode == errSecSuccess, @"Unable to generate nonce: OSStatus %i", errorCode);
    
          [randoms addObject:@(random)];
        }
    
        for (NSNumber *random in randoms) {
          if (remainingLength == 0) {
            break;
          }
    
          if (random.unsignedIntValue < characterSet.length) {
            unichar character = [characterSet characterAtIndex:random.unsignedIntValue];
            [result appendFormat:@"%C", character];
            remainingLength--;
          }
        }
      }
    
      return [result copy];
    }
        

    Você enviará o hash SHA256 do nonce com sua solicitação de login, que a Apple transmitirá inalterada na resposta. O Firebase valida a resposta fazendo hash do nonce original e comparando-o com o valor passado pela Apple.

    Rápido

    @available(iOS 13, *)
    private func sha256(_ input: String) -> String {
      let inputData = Data(input.utf8)
      let hashedData = SHA256.hash(data: inputData)
      let hashString = hashedData.compactMap {
        String(format: "%02x", $0)
      }.joined()
    
      return hashString
    }
    
        

    Objetivo-C

    - (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;
    }
        
  2. Inicie o fluxo de login da Apple, incluindo na sua solicitação o hash SHA256 do nonce e a classe delegada que tratará a resposta da Apple (veja o próximo passo):

    Rápido

    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()
    }
    

    Objetivo-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];
    }
    
  3. Lide com a resposta da Apple na sua implementação de ASAuthorizationControllerDelegate . Se o login for bem-sucedido, use o token de ID da resposta da Apple com o nonce sem hash para autenticar no Firebase:

    Rápido

    @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, including the user's full name.
          let credential = OAuthProvider.appleCredential(withIDToken: idTokenString,
                                                            rawNonce: nonce,
                                                            fullName: appleIDCredential.fullName)
          // 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)")
      }
    
    }
    

    Objetivo-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, including the user's full name.
        FIROAuthCredential *credential = [FIROAuthProvider appleCredentialWithIDToken:IDToken
                                                                             rawNonce:self.appleRawNonce
                                                                             fullName:appleIDCredential.fullName];
    
        // 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 outros provedores suportados pelo Firebase Auth, a Apple não fornece um URL de foto.

Além disso, quando o usuário opta por não compartilhar seu e-mail com o aplicativo, a Apple fornece um endereço de e-mail exclusivo para esse usuário (no formato xyz@privaterelay.appleid.com ), que ele compartilha com seu aplicativo. Se você configurou o serviço de retransmissão de e-mail privado, a Apple encaminha os e-mails enviados para o endereço anônimo para o endereço de e-mail real do usuário.

Reautenticação e vinculação de conta

O mesmo padrão pode ser usado com reauthenticateWithCredential() , que você pode usar para recuperar uma nova credencial para operações confidenciais que exigem login recente:

Rápido

// Initialize a fresh Apple credential with Firebase.
let credential = OAuthProvider.credential(
  withProviderID: "apple.com",
  IDToken: appleIdToken,
  rawNonce: rawNonce
)
// Reauthenticate current Apple user with fresh Apple credential.
Auth.auth().currentUser.reauthenticate(with: credential) { (authResult, error) in
  guard error != nil else { return }
  // Apple user successfully re-authenticated.
  // ...
}

Objetivo-C

FIRAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:@"apple.com",
                                                                   IDToken:appleIdToken,
                                                                  rawNonce:rawNonce];
[[FIRAuth auth].currentUser
    reauthenticateWithCredential:credential
                      completion:^(FIRAuthDataResult * _Nullable authResult,
                                   NSError * _Nullable error) {
  if (error) {
    // Handle error.
  }
  // Apple user successfully re-authenticated.
  // ...
}];

E você pode usar linkWithCredential() para vincular diferentes provedores de identidade a contas existentes.

Observe que a Apple exige que você obtenha o consentimento explícito dos usuários antes de vincular suas contas Apple a outros dados.

O login com a Apple não permitirá que você reutilize uma credencial de autenticação para vincular a uma conta existente. Se quiser vincular uma credencial Sign in with Apple a outra conta, você deve primeiro tentar vincular as contas usando a antiga credencial Sign in with Apple e, em seguida, examinar o erro retornado para encontrar uma nova credencial. A nova credencial estará localizada no dicionário userInfo do erro e pode ser acessada por meio da chave AuthErrorUserInfoUpdatedCredentialKey .

Por exemplo, para vincular uma conta do Facebook à conta atual do Firebase, use o token de acesso obtido ao fazer login do usuário no Facebook:

Rápido

// Initialize a Facebook credential with Firebase.
let credential = FacebookAuthProvider.credential(
  withAccessToken: AccessToken.current!.tokenString
)
// Assuming the current user is an Apple user linking a Facebook provider.
Auth.auth().currentUser.link(with: credential) { (authResult, error) in
  // Facebook credential is linked to the current Apple user.
  // The user can now sign in with Facebook or Apple to the same Firebase
  // account.
  // ...
}

Objetivo-C

// Initialize a Facebook credential with Firebase.
FacebookAuthCredential *credential = [FIRFacebookAuthProvider credentialWithAccessToken:accessToken];
// Assuming the current user is an Apple user linking a Facebook provider.
[FIRAuth.auth linkWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
  // Facebook credential is linked to the current Apple user.
  // The user can now sign in with Facebook or Apple to the same Firebase
  // account.
  // ...
}];

Revogação de token

A Apple exige que os aplicativos que oferecem suporte à criação de contas permitam que os usuários iniciem a exclusão de suas contas no aplicativo, conforme descrito nas Diretrizes de revisão da App Store

Para atender a esse requisito, implemente as seguintes etapas:

  1. Certifique-se de preencher a seção de configuração de ID de serviços e fluxo de código OAuth da configuração do provedor Sign in with Apple, conforme descrito na seção Configurar Sign in with Apple .

  2. Como o Firebase não armazena tokens de usuário quando os usuários são criados com Sign in with Apple, você deve pedir ao usuário que faça login novamente antes de revogar o token e excluir a conta.

    Rápido

    private func deleteCurrentUser() {
      do {
        let nonce = try CryptoUtils.randomNonceString()
        currentNonce = nonce
        let appleIDProvider = ASAuthorizationAppleIDProvider()
        let request = appleIDProvider.createRequest()
        request.requestedScopes = [.fullName, .email]
        request.nonce = CryptoUtils.sha256(nonce)
    
        let authorizationController = ASAuthorizationController(authorizationRequests: [request])
        authorizationController.delegate = self
        authorizationController.presentationContextProvider = self
        authorizationController.performRequests()
      } catch {
        // In the unlikely case that nonce generation fails, show error view.
        displayError(error)
      }
    }
    
    
  3. Obtenha o código de autorização do ASAuthorizationAppleIDCredential e use-o para chamar Auth.auth().revokeToken(withAuthorizationCode:) para revogar os tokens do usuário.

    Rápido

    func authorizationController(controller: ASAuthorizationController,
                                 didCompleteWithAuthorization authorization: ASAuthorization) {
      guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential
      else {
        print("Unable to retrieve AppleIDCredential")
        return
      }
    
      guard let _ = currentNonce else {
        fatalError("Invalid state: A login callback was received, but no login request was sent.")
      }
    
      guard let appleAuthCode = appleIDCredential.authorizationCode else {
        print("Unable to fetch authorization code")
        return
      }
    
      guard let authCodeString = String(data: appleAuthCode, encoding: .utf8) else {
        print("Unable to serialize auth code string from data: \(appleAuthCode.debugDescription)")
        return
      }
    
      Task {
        do {
          try await Auth.auth().revokeToken(withAuthorizationCode: authCodeString)
          try await user?.delete()
          self.updateUI()
        } catch {
          self.displayError(error)
        }
      }
    }
    
    
  4. Finalmente, exclua a conta do usuário (e todos os dados associados)

Próximos passos

Depois que um usuário faz login pela primeira vez, uma nova conta de usuário é criada e vinculada às credenciais (ou seja, nome de usuário e senha, número de telefone ou informações do provedor de autenticação) com as quais o usuário fez login. Essa nova conta é armazenada como parte do seu projeto do Firebase e pode ser usada para identificar um usuário em todos os aplicativos do seu projeto, independentemente de como o usuário faz login.

  • Nos seus aplicativos, você pode obter as informações básicas do perfil do usuário no objeto User . Consulte Gerenciar usuários .

  • Nas regras de segurança do Firebase Realtime Database e do Cloud Storage , você pode obter o ID de usuário exclusivo do usuário conectado na variável auth e usá-lo para controlar quais dados um usuário pode acessar.

Você pode permitir que os usuários façam login no seu aplicativo usando vários provedores de autenticação vinculando as credenciais do provedor de autenticação a uma conta de usuário existente.

Para desconectar um usuário, chame signOut: .

Rápido

let firebaseAuth = Auth.auth()
do {
  try firebaseAuth.signOut()
} catch let signOutError as NSError {
  print("Error signing out: %@", signOutError)
}

Objetivo-C

NSError *signOutError;
BOOL status = [[FIRAuth auth] signOut:&signOutError];
if (!status) {
  NSLog(@"Error signing out: %@", signOutError);
  return;
}

Você também pode adicionar código de tratamento de erros para toda a gama de erros de autenticação. Consulte Tratamento de erros .