Autentica con Apple en Android

Puedes permitir que los usuarios se autentiquen con Firebase mediante su ID de Apple usando el SDK de Firebase para completar el flujo de acceso de OAuth 2.0 de extremo a extremo.

Antes de comenzar

Para que los usuarios accedan con Apple, primero configura la función Iniciar sesión con Apple en el sitio para desarrolladores de Apple y habilita Apple como proveedor de acceso para tu proyecto de Firebase.

Únete al Programa para desarrolladores de Apple

Solo los miembros del Programa para desarrolladores de Apple pueden configurar la función Iniciar sesión con Apple.

Configura la función de acceso con Apple

En el sitio de desarrolladores de Apple, haz lo siguiente:

  1. Asocia tu sitio web a tu app como se describe en la primera sección de la página sobre cómo configurar el acceso con Apple para la Web. Registra la siguiente URL como una URL de retorno cuando se te solicite que lo hagas:

    https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler

    Puedes obtener el ID de tu proyecto de Firebase en la página de configuración de Firebase console.

    Cuando hayas terminado, toma nota de tu ID de servicio nuevo, ya que lo necesitarás en la siguiente sección.

  2. Crea una clave privada para acceder con Apple. Necesitarás tu ID de clave y tu clave privada nueva en la siguiente sección.
  3. Si usas alguna de las funciones de Firebase Authentication que envía correos electrónicos a los usuarios (como el acceso con vínculo por correo electrónico, la verificación de dirección de correo electrónico o la revocación de cambio de cuenta, entre otras), configura el servicio privado de retransmisión de correo electrónico de Apple y registra noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (o tu dominio de plantilla de correo electrónico personalizado) para que Apple pueda transmitir los correos electrónicos que envía Firebase Authentication a direcciones de correo electrónico anonimizadas de Apple.

Habilita Apple como proveedor de acceso

  1. Agrega Firebase al proyecto de Android. Cuando configures tu app en Firebase console, asegúrate de registrar su firma SHA-1.
  2. En Firebase console, abre la sección Auth. En la pestaña Método de acceso, habilita el proveedor Apple. Especifica el ID de servicio que creaste en la sección anterior. Además, en la sección de configuración de flujo de código OAuth, especifica tu ID de equipo de Apple junto con la clave privada y el ID de clave que creaste en la sección anterior.

Cumple con los requisitos de datos anonimizados de Apple

La función Iniciar sesión con Apple les ofrece a los usuarios la opción de anonimizar sus datos, incluida su dirección de correo electrónico, durante el acceso. Los usuarios que eligen esta opción tienen direcciones de correo electrónico con el dominio privaterelay.appleid.com. Cuando usas Iniciar sesión con Apple en tu app, debes tratar estos IDs de Apple anonimizados según las políticas para desarrolladores o las condiciones de Apple aplicables.

Esto incluye obtener los consentimientos del usuario correspondientes antes de asociar cualquier información personal que lo identifique directamente con un ID de Apple anonimizado. Cuando usas Firebase Authentication, es posible que se incluyan las siguientes acciones:

  • Vincular una dirección de correo electrónico a un ID de Apple anonimizado o viceversa
  • Vincular un número de teléfono a un ID de Apple anonimizado o viceversa
  • Vincular una credencial de redes sociales no anónima (Facebook, Google, etc.) a un ID de Apple anonimizado o viceversa

Esta lista no es exhaustiva. Consulta el Contrato de licencia del Programa para desarrolladores de Apple en la sección Membresía de tu cuenta de desarrollador para asegurarte de que tu app cumpla con los requisitos de Apple.

Maneja el flujo de acceso con el SDK de Firebase

En Android, la forma más fácil de autenticar a tus usuarios con Firebase mediante sus cuentas de Apple es controlar todo el flujo de acceso con el SDK de Firebase Android.

Para manejar el flujo de acceso con el SDK de Firebase Android, sigue estos pasos:

  1. Crea una instancia de un OAuthProvider mediante su compilador con el ID de proveedor apple.com:

    Kotlin

    val provider = OAuthProvider.newBuilder("apple.com")
    

    Java

    OAuthProvider.Builder provider = OAuthProvider.newBuilder("apple.com");
    
  2. Opcional: Especifica permisos de OAuth 2.0 adicionales aparte del valor predeterminado que deseas solicitar al proveedor de autenticación.

    Kotlin

    provider.setScopes(arrayOf("email", "name"))
    

    Java

    List<String> scopes =
        new ArrayList<String>() {
          {
            add("email");
            add("name");
          }
        };
    provider.setScopes(scopes);
    

    De manera predeterminada, cuando habilitas Una cuenta por dirección de correo electrónico, Firebase solicita los permisos de nombre y correo electrónico. Si cambias esta configuración a Varias cuentas por dirección de correo electrónico, Firebase no solicita ningún permiso de Apple, a menos que lo especifiques.

  3. Opcional: Si deseas mostrar la pantalla de acceso de Apple en un idioma que no sea inglés, establece el parámetro locale. Consulta la documentación sobre el acceso con Apple para conocer las configuraciones regionales que se admiten.

    Kotlin

    // Localize the Apple authentication screen in French.
    provider.addCustomParameter("locale", "fr")
    

    Java

    // Localize the Apple authentication screen in French.
    provider.addCustomParameter("locale", "fr");
    
  4. Autentica con Firebase usando el objeto del proveedor de OAuth. Ten en cuenta que, a diferencia de otras operaciones de FirebaseAuth, esta opción abrirá una pestaña personalizada de Chrome y tomará el control de tu IU. Por lo tanto, no hagas referencia a tu actividad en el OnSuccessListener ni en el OnFailureListener que adjuntes, ya que se desvincularán inmediatamente cuando la operación inicie la IU.

    Lo primero que debes hacer es revisar si ya recibiste una respuesta. Este método de acceso coloca tu actividad en segundo plano, lo cual significa que el sistema puede reclamarla durante el flujo de acceso. Para asegurarte de que el usuario no tenga que hacer otro intento en caso de que esto ocurra, debes revisar si ya hay un resultado.

    Para verificar si hay un resultado pendiente, llama a getPendingAuthResult():

    Kotlin

    val pending = auth.pendingAuthResult
    if (pending != null) {
        pending.addOnSuccessListener { authResult ->
            Log.d(TAG, "checkPending:onSuccess:$authResult")
            // Get the user profile with authResult.getUser() and
            // authResult.getAdditionalUserInfo(), and the ID
            // token from Apple with authResult.getCredential().
        }.addOnFailureListener { e ->
            Log.w(TAG, "checkPending:onFailure", e)
        }
    } else {
        Log.d(TAG, "pending: null")
    }
    

    Java

    mAuth = FirebaseAuth.getInstance();
    Task<AuthResult> pending = mAuth.getPendingAuthResult();
    if (pending != null) {
        pending.addOnSuccessListener(new OnSuccessListener<AuthResult>() {
            @Override
            public void onSuccess(AuthResult authResult) {
                Log.d(TAG, "checkPending:onSuccess:" + authResult);
                // Get the user profile with authResult.getUser() and
                // authResult.getAdditionalUserInfo(), and the ID
                // token from Apple with authResult.getCredential().
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                Log.w(TAG, "checkPending:onFailure", e);
            }
        });
    } else {
        Log.d(TAG, "pending: null");
    }
    

    Si no hay ningún resultado pendiente, llama a startActivityForSignInWithProvider() para iniciar el flujo de acceso:

    Kotlin

    auth.startActivityForSignInWithProvider(this, provider.build())
            .addOnSuccessListener { authResult ->
                // Sign-in successful!
                Log.d(TAG, "activitySignIn:onSuccess:${authResult.user}")
                val user = authResult.user
                // ...
            }
            .addOnFailureListener { e ->
                Log.w(TAG, "activitySignIn:onFailure", e)
            }
    

    Java

    mAuth.startActivityForSignInWithProvider(this, provider.build())
            .addOnSuccessListener(
                    new OnSuccessListener<AuthResult>() {
                        @Override
                        public void onSuccess(AuthResult authResult) {
                            // Sign-in successful!
                            Log.d(TAG, "activitySignIn:onSuccess:" + authResult.getUser());
                            FirebaseUser user = authResult.getUser();
                            // ...
                        }
                    })
            .addOnFailureListener(
                    new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception e) {
                            Log.w(TAG, "activitySignIn:onFailure", e);
                        }
                    });
    

    A diferencia de otros proveedores compatibles con Firebase Auth, Apple no proporciona una URL de foto.

    Además, cuando el usuario elige no compartir su correo electrónico con la app, Apple aprovisiona una dirección de correo electrónico única para ese usuario (con formato xyz@privaterelay.appleid.com), que comparte con tu app. Si configuraste el servicio privado de retransmisión de correo electrónico, Apple reenvía a la dirección de correo real del usuario los correos electrónicos enviados a la dirección anonimizada.

    Apple solo comparte información del usuario con las apps, como el nombre visible, cuando el usuario accede por primera vez. Por lo general, Firebase almacena el nombre visible la primera vez que un usuario accede con Apple, y puedes obtenerlo con getCurrentUser().getDisplayName(). Sin embargo, si usaste anteriormente Apple para que un usuario acceda a la app sin usar Firebase, Apple no le proporcionará a Firebase el nombre visible del usuario.

Reautenticación y vinculación de cuentas

Se puede usar el mismo patrón con startActivityForReauthenticateWithProvider(), que puedes emplear para recuperar una credencial nueva para operaciones sensibles que requieren un acceso reciente:

Kotlin

// The user is already signed-in.
val firebaseUser = auth.getCurrentUser()

firebaseUser
    .startActivityForReauthenticateWithProvider(/* activity= */ this, provider.build())
    .addOnSuccessListener( authResult -> {
        // User is re-authenticated with fresh tokens and
        // should be able to perform sensitive operations
        // like account deletion and email or password
        // update.
    })
    .addOnFailureListener( e -> {
        // Handle failure.
    })

Java

// The user is already signed-in.
FirebaseUser firebaseUser = mAuth.getCurrentUser();

firebaseUser
    .startActivityForReauthenticateWithProvider(/* activity= */ this, provider.build())
    .addOnSuccessListener(
        new OnSuccessListener<AuthResult>() {
          @Override
          public void onSuccess(AuthResult authResult) {
            // User is re-authenticated with fresh tokens and
            // should be able to perform sensitive operations
            // like account deletion and email or password
            // update.
          }
        })
    .addOnFailureListener(
        new OnFailureListener() {
          @Override
          public void onFailure(@NonNull Exception e) {
            // Handle failure.
          }
        });

Además, puedes usar linkWithCredential() para vincular diferentes proveedores de identidad a cuentas existentes.

Ten en cuenta que Apple requiere que obtengas el consentimiento explícito de los usuarios antes de vincular sus cuentas de Apple a otros datos.

Por ejemplo, para vincular una cuenta de Facebook a la cuenta actual de Firebase, usa el token de acceso que obtuviste cuando hiciste que el usuario accediera a Facebook:

Kotlin

// Initialize a Facebook credential with a Facebook access token.
val credential = FacebookAuthProvider.getCredential(token.getToken())

// Assuming the current user is an Apple user linking a Facebook provider.
mAuth.getCurrentUser().linkWithCredential(credential)
    .addOnCompleteListener(this, task -> {
        if (task.isSuccessful()) {
          // Facebook credential is linked to the current Apple user.
          // The user can now sign in to the same account
          // with either Apple or Facebook.
        }
      });

Java

// Initialize a Facebook credential with a Facebook access token.
AuthCredential credential = FacebookAuthProvider.getCredential(token.getToken());

// Assuming the current user is an Apple user linking a Facebook provider.
mAuth.getCurrentUser().linkWithCredential(credential)
    .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
      @Override
      public void onComplete(@NonNull Task<AuthResult> task) {
        if (task.isSuccessful()) {
          // Facebook credential is linked to the current Apple user.
          // The user can now sign in to the same account
          // with either Apple or Facebook.
        }
      }
    });

Avanzado: Maneja el flujo de acceso manualmente

También puedes autenticar con Firebase mediante una cuenta de Apple. Para ello, debes administrar el flujo de acceso con el SDK de JS de acceso de Apple, compilar manualmente el flujo de OAuth o utilizar una biblioteca de OAuth, como AppAuth.

  1. Para cada solicitud de acceso, genera una cadena aleatoria (un “nonce”) que utilizarás para asegurarte de que el token de ID que obtuviste se otorgó específicamente en respuesta a la solicitud de autenticación de tu app. Este paso es importante para evitar ataques de repetición.

    Puedes generar un nonce criptográficamente seguro en Android con SecureRandom, como en el siguiente ejemplo:

    Kotlin

    private fun generateNonce(length: Int): String {
        val generator = SecureRandom()
    
        val charsetDecoder = StandardCharsets.US_ASCII.newDecoder()
        charsetDecoder.onUnmappableCharacter(CodingErrorAction.IGNORE)
        charsetDecoder.onMalformedInput(CodingErrorAction.IGNORE)
    
        val bytes = ByteArray(length)
        val inBuffer = ByteBuffer.wrap(bytes)
        val outBuffer = CharBuffer.allocate(length)
        while (outBuffer.hasRemaining()) {
            generator.nextBytes(bytes)
            inBuffer.rewind()
            charsetDecoder.reset()
            charsetDecoder.decode(inBuffer, outBuffer, false)
        }
        outBuffer.flip()
        return outBuffer.toString()
    }
    

    Java

    private String generateNonce(int length) {
        SecureRandom generator = new SecureRandom();
    
        CharsetDecoder charsetDecoder = StandardCharsets.US_ASCII.newDecoder();
        charsetDecoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
        charsetDecoder.onMalformedInput(CodingErrorAction.IGNORE);
    
        byte[] bytes = new byte[length];
        ByteBuffer inBuffer = ByteBuffer.wrap(bytes);
        CharBuffer outBuffer = CharBuffer.allocate(length);
        while (outBuffer.hasRemaining()) {
            generator.nextBytes(bytes);
            inBuffer.rewind();
            charsetDecoder.reset();
            charsetDecoder.decode(inBuffer, outBuffer, false);
        }
        outBuffer.flip();
        return outBuffer.toString();
    }
    

    Luego, obtén el hash SHA246 del nonce como una cadena hexadecimal:

    Kotlin

    private fun sha256(s: String): String {
        val md = MessageDigest.getInstance("SHA-256")
        val digest = md.digest(s.toByteArray())
        val hash = StringBuilder()
        for (c in digest) {
            hash.append(String.format("%02x", c))
        }
        return hash.toString()
    }
    

    Java

    private String sha256(String s) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] digest = md.digest(s.getBytes());
        StringBuilder hash = new StringBuilder();
        for (byte c: digest) {
            hash.append(String.format("%02x", c));
        }
        return hash.toString();
    }
    

    Enviarás el hash SHA256 del nonce con tu solicitud de acceso, que Apple pasará sin cambios en la respuesta. Para validar la respuesta, Firebase genera un hash del nonce original y lo compara con el valor que pasó Apple.

  2. Inicia el flujo de acceso de Apple con tu biblioteca de OAuth o con otro método. Asegúrate de incluir el nonce con hash como parámetro en tu solicitud.

  3. Una vez que recibas la respuesta de Apple, obtén el token de ID de la respuesta y úsalo con el nonce sin hash para crear una AuthCredential:

    Kotlin

    val credential =  OAuthProvider.newCredentialBuilder("apple.com")
        .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce)
        .build()
    

    Java

    AuthCredential credential =  OAuthProvider.newCredentialBuilder("apple.com")
        .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce)
        .build();
    
  4. Usa la credencial de Firebase para autenticar con Firebase:

    Kotlin

    auth.signInWithCredential(credential)
          .addOnCompleteListener(this) { task ->
              if (task.isSuccessful) {
                // User successfully signed in with Apple ID token.
                // ...
              }
          }
    

    Java

    mAuth.signInWithCredential(credential)
        .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
          @Override
          public void onComplete(@NonNull Task<AuthResult> task) {
            if (task.isSuccessful()) {
              // User successfully signed in with Apple ID token.
              // ...
            }
          }
        });
    

Si la llamada a signInWithCredential funciona correctamente, puedes usar el método getCurrentUser para obtener los datos de la cuenta del usuario.

Revocación de tokens

Apple exige que las apps que admiten la creación de cuentas permitan que los usuarios inicien la eliminación de su cuenta dentro de la app, como se describe en las Pautas de revisión de App Store.

Además, las apps que admiten Iniciar sesión con Apple deben usar la API de REST de Iniciar sesión con Apple para revocar los tokens de usuario.

Para cumplir con este requisito, implementa los siguientes pasos:

  1. Usa el método startActivityForSignInWithProvider() para acceder con Apple y obtener AuthResult.

  2. Obtén el token de acceso para el proveedor de Apple.

    Kotlin

    val oauthCredential: OAuthCredential =  authResult.credential
    val accessToken = oauthCredential.accessToken
    

    Java

    OAuthCredential oauthCredential = (OAuthCredential) authResult.getCredential();
    String accessToken = oauthCredential.getAccessToken();
    
  3. Revoca el token con la API de revokeAccessToken.

    Kotlin

    mAuth.revokeAccessToken(accessToken)
      .addOnCompleteListener(this) { task ->
        if (task.isSuccessful) {
          // Access token successfully revoked
          // for the user ...
        }
    }
    

    Java

    mAuth.revokeAccessToken(accessToken)
        .addOnCompleteListener(this, new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
              if (task.isSuccessful()) {
                // Access token successfully revoked
                // for the user ...
              }
            }
      });
    
  1. Por último, borra la cuenta de usuario (y todos los datos asociados).

    Próximos pasos

    Cuando un usuario accede por primera vez, se crea una cuenta de usuario nueva y se la vincula con las credenciales (el nombre de usuario y la contraseña, el número de teléfono o la información del proveedor de autenticación) que el usuario utilizó para acceder. Esta cuenta nueva se almacena como parte de tu proyecto de Firebase y se puede usar para identificar a un usuario en todas las apps del proyecto, sin importar cómo acceda.

    • En tus apps, puedes obtener la información básica del perfil del usuario a partir del objeto FirebaseUser. Consulta Administra usuarios.

    • En tus Reglas de seguridad de Firebase Realtime Database y Cloud Storage, puedes obtener el ID del usuario único que accedió a partir de la variable auth y usarlo para controlar a qué datos podrá acceder.

    Para permitir que los usuarios accedan a tu app mediante varios proveedores de autenticación, puedes vincular las credenciales de estos proveedores con una cuenta de usuario existente.

    Para salir de la sesión de un usuario, llama a signOut de la siguiente manera:

    Kotlin

    Firebase.auth.signOut()

    Java

    FirebaseAuth.getInstance().signOut();