Crea tokens personalizados

Firebase te brinda control absoluto de la autenticación, ya que te permite autenticar usuarios o dispositivos mediante tokens web JSON (JWT) seguros. Estos tokens se generan en tu servidor, se pasan al dispositivo cliente y se usan para realizar la autenticación con el método signInWithCustomToken().

Para lograrlo, debes crear un extremo de servidor que acepte credenciales de acceso (como un nombre de usuario y una contraseña) y, si son válidas, muestre un JWT personalizado. De este modo, el dispositivo cliente puede usar el JWT personalizado que genera el servidor para autenticar con Firebase (en iOS+, Android y la Web). Una vez realizada la autenticación, se usará esa identidad para acceder a otros servicios de Firebase, como Firebase Realtime Database y Cloud Storage. Además, el contenido del JWT estará disponible en el objeto auth de tus reglas de seguridad de Realtime Database y en el objeto request.auth de tus reglas de seguridad de Cloud Storage.

Puedes crear un token personalizado con el SDK de Firebase Admin o puedes usar una biblioteca de JWT de terceros si tu servidor usa un lenguaje que no es compatible de forma nativa con Firebase.

Antes de comenzar

Los tokens personalizados son JWT firmados con una clave privada que se usa para firmar los elementos pertenecientes a una cuenta de servicio de Google. Existen varias formas de especificar las cuentas de servicio de Google que el SDK de Firebase Admin debe usar para firmar tokens personalizados:

  • Usa un archivo JSON de cuenta de servicio: Este método puede aplicarse en cualquier entorno, pero debes empaquetar un archivo JSON de la cuenta de servicio junto con tu código. Debes tener mucho cuidado para asegurarte de no exponer este archivo a terceros.
  • Deja que el SDK de Admin descubra una cuenta de servicio : Puedes usar este método en entornos administrados por Google, como Google Cloud Functions y App Engine. Es posible que debas configurar permisos adicionales en la consola de Google Cloud.
  • Usa un ID de cuenta de servicio: Si se usa en un entorno administrado por Google, este método firmará los tokens con la clave de la cuenta de servicio especificada. Sin embargo, el método usa un servicio web remoto, y es posible que debas configurar permisos adicionales para la cuenta de servicio en la consola de Google Cloud.

Usa un archivo JSON de cuenta de servicio

Los archivos JSON de cuenta de servicio contienen toda la información de las cuentas de servicio (lo que incluye la clave privada de RSA). Puedes descargarlos desde Firebase console. Sigue las instrucciones de configuración del SDK de Admin y obtén más información para inicializarlo con un archivo JSON de cuenta de servicio.

Este método de inicialización es compatible con una amplia variedad de implementaciones de los SDK de Admin. También permite que este SDK cree y firme tokens personalizados de forma local sin llamadas a API remotas. La desventaja principal de este enfoque es que debes empaquetar un archivo JSON de cuenta de servicio junto con tu código. Además, ten en cuenta que la clave privada de un archivo JSON de una cuenta de servicio tiene información confidencial, por lo que debes tener cuidado para proteger su confidencialidad. Específicamente, no agregues archivos JSON de cuentas de servicio a controles de versión públicos.

Deja que el SDK de Admin descubra una cuenta de servicio

Si implementas tu código en un entorno administrado por Google, el SDK de Admin intentará descubrir de forma automática una manera de firmar tokens personalizados:

  • Si implementas tu código en el entorno estándar de App Engine para Java, Python o Go, el SDK de Admin podrá usar el servicio App Identity del entorno para firmar tokens personalizados. Este servicio permite firmar los datos con una cuenta de servicio que Google App Engine aprovisiona para la app.

  • Si implementas tu código en otro entorno administrado (p. ej., Google Cloud Functions o Google Compute Engine), el SDK de Firebase Admin puede descubrir automáticamente una cadena de ID de cuenta de servicio del servidor de metadatos local. A continuación, se usa el ID de la cuenta de servicio descubierto junto con el servicio IAM para firmar tokens de forma remota.

Para usar estos métodos de firma, inicializa el SDK con las credenciales predeterminadas de la aplicación de Google y no especifiques la cadena del ID de la cuenta de servicio:

Node.js

initializeApp();

Java

FirebaseApp.initializeApp();

Python

default_app = firebase_admin.initialize_app()

Go

app, err := firebase.NewApp(context.Background(), nil)
if err != nil {
	log.Fatalf("error initializing app: %v\n", err)
}

C#

FirebaseApp.Create();

Para probar el mismo código de forma local, descarga el archivo JSON de la cuenta de servicio y configura la variable del entorno GOOGLE_APPLICATION_CREDENTIALS para que apunte a él.

El SDK de Firebase Admin descubrirá una cadena del ID de cuenta de servicio cuando tu código cree un token personalizado por primera vez. El resultado se almacena en caché y se vuelve a usar en operaciones de firma de token posteriores. El ID de cuenta de servicio descubierto automáticamente suele corresponder a una de las cuentas de servicio predeterminadas de Google Cloud:

De igual forma que los IDs de cuentas de servicio especificados de manera explícita, los IDs de cuentas de servicio con descubrimiento automático deben tener el permiso iam.serviceAccounts.signBlob para que funcione la creación de tokens personalizados. Es posible que debas usar la sección IAM y administración en la consola de Google Cloud para otorgar los permisos necesarios a las cuentas de servicio predeterminadas. Para obtener más información, revisa la sección de solución de problemas que se encuentra más abajo.

Usa un ID de cuenta de servicio

Para mantener la coherencia entre varias partes de tu aplicación, puedes especificar un ID de cuenta de servicio cuyas claves se usen para firmar los tokens cuando se ejecute en un entorno administrado por Google. Esto permite que las políticas de IAM sean más simples y seguras, y evita que se deba incluir el archivo JSON de la cuenta de servicio en el código.

Puedes encontrar el ID de la cuenta de servicio en la consola de Google Cloud o en el campo client_email de un archivo JSON de cuenta de servicio que hayas descargado. Los IDs de cuenta de servicio son direcciones de correo electrónico con el siguiente formato: <client-id>@<project-id>.iam.gserviceaccount.com. Permiten identificar cuentas de servicio de manera única en proyectos de Firebase y Google Cloud.

Para crear tokens personalizados con un ID de cuenta de servicio distinto, inicializa el SDK como se muestra a continuación:

Node.js

initializeApp({
  serviceAccountId: 'my-client-id@my-project-id.iam.gserviceaccount.com',
});

Java

FirebaseOptions options = FirebaseOptions.builder()
    .setCredentials(GoogleCredentials.getApplicationDefault())
    .setServiceAccountId("my-client-id@my-project-id.iam.gserviceaccount.com")
    .build();
FirebaseApp.initializeApp(options);

Python

options = {
    'serviceAccountId': 'my-client-id@my-project-id.iam.gserviceaccount.com',
}
firebase_admin.initialize_app(options=options)

Go

conf := &firebase.Config{
	ServiceAccountID: "my-client-id@my-project-id.iam.gserviceaccount.com",
}
app, err := firebase.NewApp(context.Background(), conf)
if err != nil {
	log.Fatalf("error initializing app: %v\n", err)
}

C#

FirebaseApp.Create(new AppOptions()
{
    Credential = GoogleCredential.GetApplicationDefault(),
    ServiceAccountId = "my-client-id@my-project-id.iam.gserviceaccount.com",
});

Los IDs de cuenta de servicio no son información sensible, por lo que no debes preocuparte por si quedan expuestos. Sin embargo, el SDK de Firebase Admin debe invocar un servicio remoto para firmar tokens personalizados con la cuenta de servicio especificada. Además, debes asegurarte de que la cuenta de servicio que usa el SDK de Admin para realizar esta llamada (normalmente {project-name}@appspot.gserviceaccount.com) tenga el permiso iam.serviceAccounts.signBlob. Para obtener más información, revisa la sección de solución de problemas que se encuentra más abajo.

Crea tokens personalizados con el SDK de Firebase Admin

El SDK de Firebase Admin tiene un método incorporado para crear tokens personalizados. Como mínimo, debes proporcionar un uid, que puede ser cualquier cadena que identifique de manera única al usuario o dispositivo que estás autenticando. Estos tokens vencen después de una hora.

Node.js

const uid = 'some-uid';

getAuth()
  .createCustomToken(uid)
  .then((customToken) => {
    // Send token back to client
  })
  .catch((error) => {
    console.log('Error creating custom token:', error);
  });

Java

String uid = "some-uid";

String customToken = FirebaseAuth.getInstance().createCustomToken(uid);
// Send token back to client

Python

uid = 'some-uid'

custom_token = auth.create_custom_token(uid)

Go

client, err := app.Auth(context.Background())
if err != nil {
	log.Fatalf("error getting Auth client: %v\n", err)
}

token, err := client.CustomToken(ctx, "some-uid")
if err != nil {
	log.Fatalf("error minting custom token: %v\n", err)
}

log.Printf("Got custom token: %v\n", token)

C#

var uid = "some-uid";

string customToken = await FirebaseAuth.DefaultInstance.CreateCustomTokenAsync(uid);
// Send token back to client

Opcionalmente, también puedes especificar reclamaciones adicionales para que se incluyan en el token personalizado. En el ejemplo que se muestra a continuación, se agregó el campo premiumAccount al token personalizado, que estará disponible en los objetos auth/request.auth de tus reglas de seguridad:

Node.js

const userId = 'some-uid';
const additionalClaims = {
  premiumAccount: true,
};

getAuth()
  .createCustomToken(userId, additionalClaims)
  .then((customToken) => {
    // Send token back to client
  })
  .catch((error) => {
    console.log('Error creating custom token:', error);
  });

Java

String uid = "some-uid";
Map<String, Object> additionalClaims = new HashMap<String, Object>();
additionalClaims.put("premiumAccount", true);

String customToken = FirebaseAuth.getInstance()
    .createCustomToken(uid, additionalClaims);
// Send token back to client

Python

uid = 'some-uid'
additional_claims = {
    'premiumAccount': True
}

custom_token = auth.create_custom_token(uid, additional_claims)

Go

client, err := app.Auth(context.Background())
if err != nil {
	log.Fatalf("error getting Auth client: %v\n", err)
}

claims := map[string]interface{}{
	"premiumAccount": true,
}

token, err := client.CustomTokenWithClaims(ctx, "some-uid", claims)
if err != nil {
	log.Fatalf("error minting custom token: %v\n", err)
}

log.Printf("Got custom token: %v\n", token)

C#

var uid = "some-uid";
var additionalClaims = new Dictionary<string, object>()
{
    { "premiumAccount", true },
};

string customToken = await FirebaseAuth.DefaultInstance
    .CreateCustomTokenAsync(uid, additionalClaims);
// Send token back to client

Nombres de los tokens personalizados reservados

Accede con tokens personalizados en los clientes

Después de crear un token personalizado, deberías enviarlo a la app cliente. La aplicación cliente se autentica con el token personalizado mediante una llamada a signInWithCustomToken():

iOS+

Objective-C
[[FIRAuth auth] signInWithCustomToken:customToken
                           completion:^(FIRAuthDataResult * _Nullable authResult,
                                        NSError * _Nullable error) {
  // ...
}];
Swift
Auth.auth().signIn(withCustomToken: customToken ?? "") { user, error in
  // ...
}

Android

mAuth.signInWithCustomToken(mCustomToken)
        .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
            @Override
            public void onComplete(@NonNull Task<AuthResult> task) {
                if (task.isSuccessful()) {
                    // Sign in success, update UI with the signed-in user's information
                    Log.d(TAG, "signInWithCustomToken:success");
                    FirebaseUser user = mAuth.getCurrentUser();
                    updateUI(user);
                } else {
                    // If sign in fails, display a message to the user.
                    Log.w(TAG, "signInWithCustomToken:failure", task.getException());
                    Toast.makeText(CustomAuthActivity.this, "Authentication failed.",
                            Toast.LENGTH_SHORT).show();
                    updateUI(null);
                }
            }
        });

Unity

auth.SignInWithCustomTokenAsync(custom_token).ContinueWith(task => {
  if (task.IsCanceled) {
    Debug.LogError("SignInWithCustomTokenAsync was canceled.");
    return;
  }
  if (task.IsFaulted) {
    Debug.LogError("SignInWithCustomTokenAsync encountered an error: " + task.Exception);
    return;
  }

  Firebase.Auth.AuthResult result = task.Result;
  Debug.LogFormat("User signed in successfully: {0} ({1})",
      result.User.DisplayName, result.User.UserId);
});

C++

firebase::Future<firebase::auth::AuthResult> result =
    auth->SignInWithCustomToken(custom_token);

API con espacio de nombres web

firebase.auth().signInWithCustomToken(token)
  .then((userCredential) => {
    // Signed in
    var user = userCredential.user;
    // ...
  })
  .catch((error) => {
    var errorCode = error.code;
    var errorMessage = error.message;
    // ...
  });

API modular web

import { getAuth, signInWithCustomToken } from "firebase/auth";

const auth = getAuth();
signInWithCustomToken(auth, token)
  .then((userCredential) => {
    // Signed in
    const user = userCredential.user;
    // ...
  })
  .catch((error) => {
    const errorCode = error.code;
    const errorMessage = error.message;
    // ...
  });

Si la autenticación se ejecuta correctamente, el usuario accederá a la app cliente con la cuenta que se especifica en el uid incluido en el token personalizado. Si la cuenta no existía antes, se creará un registro de ese usuario.

De la misma manera que con otros métodos de acceso (como signInWithEmailAndPassword() y signInWithCredential()), el objeto auth en tus reglas de seguridad de Realtime Database y el objeto request.auth en las reglas de seguridad de Cloud Storage se propagarán con el uid del usuario. En este caso, el uid será el que especificaste cuando se generó el token personalizado.

Reglas de Database

{
  "rules": {
    "adminContent": {
      ".read": "auth.uid === 'some-uid'"
    }
  }
}

Reglas de Storage

service firebase.storage {
  match /b/<your-firebase-storage-bucket>/o {
    match /adminContent/{filename} {
      allow read, write: if request.auth != null && request.auth.uid == "some-uid";
    }
  }
}

Si el token personalizado contiene reclamaciones adicionales, se puede hacer referencia a ellos en el objeto auth.token (Firebase Realtime Database) o request.auth.token (Cloud Storage) en tus reglas:

Reglas de Database

{
  "rules": {
    "premiumContent": {
      ".read": "auth.token.premiumAccount === true"
    }
  }
}

Reglas de Storage

service firebase.storage {
  match /b/<your-firebase-storage-bucket>/o {
    match /premiumContent/{filename} {
      allow read, write: if request.auth.token.premiumAccount == true;
    }
  }
}

Crea tokens personalizados con una biblioteca de JWT de terceros

Si tu backend usa un lenguaje que no tiene un SDK de Firebase Admin oficial, puedes crear tokens personalizados de forma manual. En primer lugar, busca una biblioteca de JWT de terceros para tu lenguaje. Después, usa esa biblioteca de JWT para acuñar un JWT que incluya las siguientes reclamaciones:

Reclamaciones de tokens personalizados
alg Algoritmo "RS256"
iss Emisor Dirección de correo electrónico de la cuenta de servicio del proyecto
sub Asunto Dirección de correo electrónico de la cuenta de servicio del proyecto
aud Público "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat Hora de emisión Hora actual, en segundos transcurridos desde la época UNIX
exp Hora de vencimiento Hora de vencimiento del token, en segundos transcurridos desde la época UNIX Puede ser un máximo de 3,600 segundos más tarde de iat.
Nota: Ten en cuenta que esto solo controla la hora de vencimiento del token personalizado en sí. Sin embargo, cuando haces que un usuario acceda con signInWithCustomToken(), su acceso al dispositivo se mantendrá hasta que esa sesión deje de ser válida o el usuario la cierre.
uid El identificador único del usuario que accedió debe ser una cadena que contenga entre 1 y 128 caracteres. Los elementos uid más cortos tienen un mejor rendimiento.
claims (opcional) Reclamaciones personalizadas opcionales (pueden incluirse en las variables auth/request.auth de las reglas de seguridad)

A continuación, se muestran algunas implementaciones de ejemplo para crear tokens personalizados en diversos lenguajes que no son compatibles con el SDK de Firebase Admin:

PHP

Usa php-jwt:

// Requires: composer require firebase/php-jwt
use Firebase\JWT\JWT;

// Get your service account's email address and private key from the JSON key file
$service_account_email = "abc-123@a-b-c-123.iam.gserviceaccount.com";
$private_key = "-----BEGIN PRIVATE KEY-----...";

function create_custom_token($uid, $is_premium_account) {
  global $service_account_email, $private_key;

  $now_seconds = time();
  $payload = array(
    "iss" => $service_account_email,
    "sub" => $service_account_email,
    "aud" => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
    "iat" => $now_seconds,
    "exp" => $now_seconds+(60*60),  // Maximum expiration time is one hour
    "uid" => $uid,
    "claims" => array(
      "premium_account" => $is_premium_account
    )
  );
  return JWT::encode($payload, $private_key, "RS256");
}

Ruby

Usa ruby-jwt:

require "jwt"

# Get your service account's email address and private key from the JSON key file
$service_account_email = "service-account@my-project-abc123.iam.gserviceaccount.com"
$private_key = OpenSSL::PKey::RSA.new "-----BEGIN PRIVATE KEY-----\n..."

def create_custom_token(uid, is_premium_account)
  now_seconds = Time.now.to_i
  payload = {:iss => $service_account_email,
             :sub => $service_account_email,
             :aud => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
             :iat => now_seconds,
             :exp => now_seconds+(60*60), # Maximum expiration time is one hour
             :uid => uid,
             :claims => {:premium_account => is_premium_account}}
  JWT.encode payload, $private_key, "RS256"
end

Después de crear el token personalizado, envíalo a tu app cliente para usarlo en la autenticación con Firebase. Consulta las muestras de código que aparecen más arriba para ver cómo hacerlo.

Solución de problemas

En esta sección, se presentan soluciones a algunos problemas comunes que los desarrolladores pueden enfrentar en el momento de crear tokens personalizados.

No se habilita la API de IAM

Si especificas un ID de cuenta de servicio para firmar los tokens, es posible que aparezca un error similar al que se muestra a continuación:

Identity and Access Management (IAM) API has not been used in project
1234567890 before or it is disabled. Enable it by visiting
https://console.developers.google.com/apis/api/iam.googleapis.com/overview?project=1234567890
then retry. If you enabled this API recently, wait a few minutes for the action
to propagate to our systems and retry.

El SDK de Firebase Admin usa la API de IAM para firmar los tokens. Este error indica que la API no se encuentra habilitada para tu proyecto de Firebase. Abre el vínculo del mensaje de error en un navegador web y haz clic en el botón “Habilitar API” para habilitarla en tu proyecto.

A la cuenta de servicio le faltan permisos obligatorios

Si la cuenta de servicio que ejecuta el SDK de Firebase Admin no tiene el permiso iam.serviceAccounts.signBlob, es posible que se muestre un mensaje de error como el que aparece a continuación:

Permission iam.serviceAccounts.signBlob is required to perform this operation
on service account projects/-/serviceAccounts/{your-service-account-id}.

La manera más fácil de solucionar este problema es otorgar la función de IAM “Creador de tokens de cuenta de servicio” a la cuenta de servicio apropiada, normalmente {project-name}@appspot.gserviceaccount.com:

  1. Abre la página IAM y administración en la consola de Google Cloud.
  2. Selecciona el proyecto y haz clic en “Continuar”.
  3. Haz clic en el ícono de edición correspondiente a la cuenta de servicio que deseas actualizar.
  4. Haz clic en “Agregar otro rol”.
  5. Escribe “Creador de tokens de cuenta de servicio” en el filtro de búsqueda y selecciona esta opción en los resultados.
  6. Haz clic en “Guardar” para confirmar que otorgaste el rol.

Consulta la documentación de IAM para obtener más información sobre este proceso o para saber cómo actualizar las funciones con la herramienta de línea de comandos de gcloud.

No se puede determinar la cuenta de servicio

Si recibes un mensaje de error similar al siguiente, el SDK de Firebase Admin no se inicializó de forma apropiada.

Failed to determine service account ID. Initialize the SDK with service account
credentials or specify a service account ID with iam.serviceAccounts.signBlob
permission.

Si dependes del SDK para descubrir automáticamente un ID de cuenta de servicio, asegúrate de implementar el código en un entorno administrado por Google con un servidor de metadatos. De lo contrario, asegúrate de especificar el archivo JSON o el ID de la cuenta de servicio cuando inicialices el SDK.