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á
esta 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 tu
Realtime Database Security Rules y el
objeto request.auth
de tu
Cloud Storage Security Rules.
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 ser uno de los ID de cuentas proporcionadas por Google Cloud:
- Cuenta de servicio predeterminada de Compute Engine
- Cuenta de servicio predeterminada de Cloud Functions
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
. Identifican de manera inequívoca
cuentas de servicio en proyectos de Firebase y de 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);
Web
firebase.auth().signInWithCustomToken(token)
.then((userCredential) => {
// Signed in
var user = userCredential.user;
// ...
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
// ...
});
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 tu Realtime Database Security Rules y
el objeto request.auth
en tu
Cloud Storage Security Rules se
completará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) de 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.
Soluciona 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
:
- Abre IAM y administración. en la consola de Google Cloud.
- Selecciona el proyecto y haz clic en “Continuar”.
- Haz clic en el ícono de edición correspondiente a la cuenta de servicio que deseas actualizar.
- Haz clic en “Agregar otro rol”.
- Escribe “Creador de tokens de cuenta de servicio” en el filtro de búsqueda y selecciona esta opción en los resultados.
- 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.