Creazione di token personalizzati

Firebase ti offre il controllo completo sull'autenticazione consentendoti di autenticare utenti o dispositivi utilizzando token web JSON (JWT) sicuri. Genera questi token sul tuo server, poi trasmettili a un dispositivo client e utilizzali per l'autenticazione tramite il metodo signInWithCustomToken().

Per farlo, devi creare un endpoint del server che accetti le credenziali di accesso, ad esempio un nome utente e una password, e, se le credenziali sono valide, restituisce un JWT personalizzato. Il token JWT personalizzato restituito dal server può essere utilizzato da un dispositivo client per l'autenticazione con Firebase (iOS e versioni successive, Android, web). Una volta autenticata, questa identità verrà utilizzata per accedere ad altri servizi Firebase, come Firebase Realtime Database e Cloud Storage. Inoltre, i contenuti del JWT saranno disponibili nell'oggetto auth in Realtime Database Security Rules e nell'oggetto request.auth in Cloud Storage Security Rules.

Puoi creare un token personalizzato con l'SDK Firebase Admin o utilizzare una libreria JWT di terze parti se il tuo server è scritto in un linguaggio non supportato nativamente da Firebase.

Prima di iniziare

I token personalizzati sono JWT firmati in cui la chiave privata utilizzata per la firma appartiene a un account di servizio Google. Esistono diversi modi per specificare l'account di servizio Google da utilizzare dall'SDK Firebase Admin per la firma dei token personalizzati:

  • Utilizzo di un file JSON dell'account di servizio: questo metodo può essere utilizzato in qualsiasi ambiente, ma richiede di pacchettizzare un file JSON dell'account di servizio insieme al codice. È necessario prestare particolare attenzione per assicurarsi che il file JSON dell'account di servizio non sia esposto a terze parti.
  • Consentire all'SDK Admin di rilevare un account di servizio: questo metodo può essere utilizzato in ambienti gestiti da Google, come Google Cloud Functions e App Engine. Potresti dover configurare alcune autorizzazioni aggiuntive tramite la console Google Cloud.
  • Utilizzo di un ID account di servizio: se utilizzato in un ambiente gestito da Google, questo metodo firmerà i token utilizzando la chiave dell'account di servizio specificato. Tuttavia, utilizza un web service remoto e potrebbe essere necessario configurare autorizzazioni aggiuntive per questo account di servizio tramite la console Google Cloud.

Utilizzo di un file JSON dell'account di servizio

I file JSON degli account di servizio contengono tutte le informazioni corrispondenti agli account di servizio (inclusa la chiave privata RSA). Possono essere scaricati dalla console Firebase. Segui le istruzioni di configurazione dell'SDK Admin per ulteriori informazioni su come inizializzare l'SDK Admin con un file JSON dell'account di servizio.

Questo metodo di inizializzazione è adatto a una vasta gamma di implementazioni dell'SDK Admin. Inoltre, consente all'SDK Admin di creare e firmare token personalizzati localmente, senza effettuare chiamate API da remoto. Lo svantaggio principale di questo approccio è che richiede di pacchettizzare un file JSON dell'account di servizio insieme al codice. Tieni inoltre presente che la chiave privata in un file JSON dell'account di servizio è costituita da informazioni sensibili e deve essere trattata con particolare attenzione per mantenerla confidenziale. In particolare, evita di aggiungere file JSON degli account di servizio al controllo della versione pubblico.

Consentire all'SDK Admin di rilevare un account di servizio

Se il codice viene implementato in un ambiente gestito da Google, l'SDK Admin può tentare di rilevare automaticamente un modo per firmare i token personalizzati:

  • Se il codice viene disegnato nell'ambiente standard App Engine per Java, Python o Go, l'SDK Admin può utilizzare il servizio App Identity presente in quell'ambiente per firmare i token personalizzati. Il servizio App Identity firma i dati utilizzando un account di servizio di cui è stato eseguito il provisioning per la tua app da Google App Engine.

  • Se il codice viene disegnato in un altro ambiente gestito (ad es. Google Cloud Functions, Google Compute Engine), l'SDK Firebase Admin può rilevare automaticamente una stringa di ID account di servizio dal server di metadati locale. L'ID account di servizio rilevato viene poi utilizzato in combinazione con il servizio IAM per firmare i token da remoto.

Per utilizzare questi metodi di firma, inizializza l'SDK con le credenziali predefinite dell'applicazione Google e non specificare una stringa ID account di servizio:

Node.js

initializeApp();

Java

FirebaseApp.initializeApp();

Python

default_app = firebase_admin.initialize_app()

Vai

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

C#

FirebaseApp.Create();

Per testare lo stesso codice localmente, scarica un file JSON dell'account di servizio e imposta la variabile di ambiente GOOGLE_APPLICATION_CREDENTIALS in modo che rimandi al file.

Se l'SDK Firebase Admin deve scoprire una stringa ID account di servizio, lo fa quando il codice crea un token personalizzato per la prima volta. Il risultato viene memorizzato nella cache e riutilizzato per le successive operazioni di firma dei token. L'ID account di servizio rilevato automaticamente è in genere uno degli account di servizio predefiniti forniti dal Google Cloud:

Come per gli ID account di servizio specificati esplicitamente, gli ID account di servizio rilevati automaticamente devono disporre dell'autorizzazione iam.serviceAccounts.signBlob per la creazione di token personalizzati. Potresti dover utilizzare la sezione IAM e amministrazione della console Google Cloud per concedere agli account di servizio predefiniti le autorizzazioni necessarie. Per ulteriori dettagli, consulta la sezione relativa alla risoluzione dei problemi riportata di seguito.

Utilizzo di un ID account di servizio

Per mantenere la coerenza tra le varie parti dell'applicazione, puoi specificare un ID account di servizio le cui chiavi verranno utilizzate per firmare i token durante l'esecuzione in un ambiente gestito da Google. In questo modo, i criteri IAM possono essere semplificati e resi più sicuri ed è possibile evitare di dover includere il file JSON dell'account di servizio nel codice.

L'ID account di servizio è disponibile nella console Google Cloud o nel campo client_email di un file JSON dell'account di servizio scaricato. Gli ID account di servizio sono indirizzi email con il seguente formato: <client-id>@<project-id>.iam.gserviceaccount.com. Identificano in modo univoco gli account di servizio nei progetti Firebase e Google Cloud.

Per creare token personalizzati utilizzando un ID account di servizio separato, inizializza l'SDK come mostrato di seguito:

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)

Vai

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",
});

Gli ID account di servizio non sono informazioni sensibili e pertanto la loro esposizione è irrilevante. Tuttavia, per firmare i token personalizzati con l'account di servizio specificato, l'SDK Firebase Admin deve invocare un servizio remoto. Inoltre, devi assicurarti che l'account di servizio utilizzato dall'SDK Admin per effettuare questa chiamata, in genere {project-name}@appspot.gserviceaccount.com, abbia l'autorizzazione iam.serviceAccounts.signBlob. Per ulteriori dettagli, consulta la sezione relativa alla risoluzione dei problemi riportata di seguito.

Creare token personalizzati utilizzando l'SDK Firebase Admin

L'SDK Firebase Admin dispone di un metodo integrato per la creazione di token personalizzati. Come minimo, devi fornire un uid, che può essere qualsiasi stringa, ma deve identificare in modo univoco l'utente o il dispositivo che stai autenticando. Questi token scadono tra un'ora.

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)

Vai

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

Se vuoi, puoi anche specificare altri claim da includere nel token personalizzato. Ad esempio, di seguito è stato aggiunto un campo premiumAccount al token personalizzato, che sarà disponibile negli oggetti auth / request.auth nelle regole di sicurezza:

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)

Vai

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

Nomi di token personalizzati riservati

Accedere utilizzando token personalizzati sui client

Dopo aver creato un token personalizzato, devi inviarlo all'app client. L'app client si autentica con il token personalizzato chiamandosignInWithCustomToken():

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;
    // ...
  });

Se l'autenticazione va a buon fine, l'utente avrà eseguito l'accesso alla tua app cliente con l'account specificato dal valore uid incluso nel token personalizzato. Se l'account non esisteva in precedenza, verrà creato un record per quell'utente.

Come per altri metodi di accesso (ad esempio signInWithEmailAndPassword() e signInWithCredential()), l'oggetto auth nel Realtime Database Security Rules e l'oggetto request.auth nel Cloud Storage Security Rules verranno compilati con il uid dell'utente. In questo caso, uid sarà quello che hai specificato durante la generazione del token personalizzato.

Regole del database

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

Regole di archiviazione

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";
    }
  }
}

Se il token personalizzato contiene altri claim, è possibile farvi riferimento dall'oggetto auth.token (Firebase Realtime Database) o request.auth.token (Cloud Storage) nelle regole:

Regole del database

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

Regole di archiviazione

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

Creare token personalizzati utilizzando una libreria JWT di terze parti

Se il tuo backend è in una lingua per la quale non è disponibile un SDK Firebase Admin ufficiale, puoi comunque creare manualmente token personalizzati. Innanzitutto, trova una libreria JWT di terze parti per la tua lingua. Poi, utilizza la libreria JWT per coniare un JWT che includa le seguenti attestazioni:

Richieste di token personalizzati
alg Algoritmo "RS256"
iss Emittente L'indirizzo email dell'account di servizio del progetto
sub Oggetto L'indirizzo email dell'account di servizio del progetto
aud Pubblico "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat Data/ora di emissione L'ora corrente, in secondi dall'epoca UNIX
exp Scadenza La data e l'ora in cui scade il token, espressa in secondi dall'epoca UNIX. Deve essere al massimo 3600 secondi dopo il valore iat.
Nota: questo controlla solo la data di scadenza del token personalizzato stesso. Tuttavia, dopo che un utente ha eseguito l'accesso utilizzando signInWithCustomToken(), l'accesso al dispositivo rimarrà attivo finché la sessione non viene invalidata o l'utente non si disconnette.
uid L'identificatore univoco dell'utente che ha eseguito l'accesso deve essere una stringa compresa tra 1 e 128 caratteri, inclusi. I uid più brevi offrono un rendimento migliore.
claims (facoltativo) Dichiarazioni personalizzate facoltative da includere nelle variabili auth/ request.auth delle regole di sicurezza

Ecco alcuni esempi di implementazione di come creare token personalizzati in una serie di lingue non supportate dall'SDK Firebase Admin:

PHP

Utilizzo di 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

Utilizzo di 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

Dopo aver creato il token personalizzato, invialo all'app client per utilizzarlo per autenticarti con Firebase. Per scoprire come, consulta gli esempi di codice riportati sopra.

Risoluzione dei problemi

Questa sezione illustra alcuni problemi comuni che gli sviluppatori potrebbero riscontrare durante la creazione di token personalizzati e come risolverli.

API IAM non abilitata

Se specifichi un ID account di servizio per la firma dei token, potresti ricevere un errore simile al seguente:

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.

L'SDK Firebase Admin utilizza l'API IAM per firmare i token. Questo errore indica che l'API IAM non è attualmente abilitata per il tuo progetto Firebase. Apri il link nel messaggio di errore in un browser web e fai clic sul pulsante "Attiva API" per attivarla per il tuo progetto.

L'account di servizio non dispone delle autorizzazioni richieste

Se l'account di servizio con cui viene eseguito l'SDK Firebase Admin non dispone dell'autorizzazioneiam.serviceAccounts.signBlob, potresti visualizzare un messaggio di errore simile al seguente:

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

Il modo più semplice per risolvere il problema è concedere il ruolo IAM "Creatore token account di servizio" all'account di servizio in questione, in genere{project-name}@appspot.gserviceaccount.com:

  1. Apri la pagina IAM e amministrazione nella console Google Cloud.
  2. Seleziona il progetto e fai clic su "Continua".
  3. Fai clic sull'icona di modifica corrispondente all'account di servizio che vuoi aggiornare.
  4. Fai clic su "Aggiungi un altro ruolo".
  5. Digita "Creatore token account di servizio" nel filtro di ricerca e selezionalo dai risultati.
  6. Fai clic su "Salva" per confermare l'assegnazione del ruolo.

Per ulteriori dettagli su questa procedura, consulta la documentazione di IAM o scopri come aggiornare i ruoli utilizzando gli strumenti a riga di comando gcloud.

Impossibile determinare l'account di servizio

Se ricevi un messaggio di errore simile al seguente, l'SDK Firebase Admin non è stato inizializzato correttamente.

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

Se utilizzi l'SDK per rilevare automaticamente un ID account di servizio, assicurati che il codice sia dipiegato in un ambiente Google gestito con un server di metadati. In caso contrario, assicurati di specificare il file JSON dell'account di servizio o l'ID account di servizio all'inizializzazione dell'SDK.