Tworzenie tokenów niestandardowych

Firebase zapewnia pełną kontrolę nad uwierzytelnianiem, umożliwiając uwierzytelnianie użytkowników lub urządzeń za pomocą bezpiecznych tokenów internetowych JSON (JWT). Te tokeny generujesz na serwerze, przekazujesz je z powrotem do urządzenia klienta, a następnie używasz ich do uwierzytelniania za pomocą metody signInWithCustomToken().

Aby to zrobić, musisz utworzyć punkt końcowy serwera, który akceptuje dane logowania, takie jak nazwa użytkownika i hasło, i jeśli są one prawidłowe, zwraca niestandardowy token JWT. Niestandardowy token JWT zwrócony z serwera może być następnie używany przez urządzenie klienta do uwierzytelniania w Firebase (iOS+, Android, sieć). Po uwierzytelnieniu ta tożsamość będzie używana podczas uzyskiwania dostępu do innych usług Firebase, takich jak Firebase Realtime Database i Cloud Storage. Ponadto zawartość tokena JWT będzie dostępna w obiekcie auth w Realtime Database Security Rules i obiekcie request.auth w Cloud Storage Security Rules.

Możesz utworzyć token niestandardowy za pomocą pakietu Firebase Admin SDK lub użyć zewnętrznej biblioteki JWT, jeśli Twój serwer jest napisany w języku, którego Firebase nie obsługuje natywnie.

Zanim zaczniesz

Tokeny niestandardowe to podpisane tokeny JWT, w których klucz prywatny używany do podpisywania należy do konta usługi Google. Istnieje kilka sposobów określenia konta usługi Google, które powinno być używane przez pakiet Firebase Admin SDK do podpisywania tokenów niestandardowych:

  • Umożliwienie pakietowi Admin SDK wykrycia konta usługi – tej metody można używać w środowiskach zarządzanych przez Google, takich jak Google Cloud Functions i App Engine. Może być konieczne skonfigurowanie dodatkowych uprawnień w konsoli Google Cloud.
  • Użycie identyfikatora konta usługi – ta metoda podpisuje tokeny przy użyciu kluczy zarządzanych przez Google dla określonego konta usługi, dzięki czemu nie musisz samodzielnie obsługiwać kluczy prywatnych. Używa zdalnej usługi internetowej i może być konieczne skonfigurowanie dodatkowych uprawnień dla tego konta usługi w konsoli Google Cloud.
  • Użycie pliku klucza konta usługi – użyj tej metody tylko wtedy, gdy kod jest uruchamiany poza środowiskiem zarządzanym przez Google i nie możesz użyć żadnej z powyższych metod. Wymaga utworzenia, przechowywania i ochrony długotrwałego klucza prywatnego.

Zalecamy 2 pierwsze metody, ponieważ nie wymagają one pobierania kluczy kont usługi ani zarządzania nimi.

Umożliwianie pakietowi Admin SDK wykrywania konta usługi

Jeśli kod jest wdrażany w środowisku zarządzanym przez Google, pakiet Admin SDK może spróbować automatycznie wykryć sposób podpisywania tokenów niestandardowych:

  • Jeśli Twój kod jest wdrożony w środowisku standardowym App Engine dla Javy, Pythona lub Go, pakiet Admin SDK może używać usługi tożsamości aplikacji obecnej w tym środowisku do podpisywania tokenów niestandardowych. Usługa tożsamości aplikacji podpisuje dane za pomocą konta usługi udostępnionego dla aplikacji przez Google App Engine.

  • Jeśli kod jest wdrażany w innym środowisku zarządzanym (np. Google Cloud Functions, Google Compute Engine), pakiet Firebase Admin SDK może automatycznie wykryć ciąg identyfikatora konta usługi z lokalnego serwera metadanych. Wykryty identyfikator konta usługi jest następnie używany w połączeniu z usługą uprawnień do zdalnego podpisywania tokenów.

Aby korzystać z tych metod podpisywania, zainicjuj pakiet SDK przy użyciu domyślnych danych logowania aplikacji Google i nie podawaj ciągu identyfikatora konta usługi:

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

Aby przetestować ten sam kod lokalnie bez pobierania klucza konta usługi, zaloguj się za pomocą własnych danych logowania użytkownika przy użyciu Google Cloud CLI:

gcloud auth application-default login

Na komputerze lokalnym nie ma serwera metadanych, więc pakiet Admin SDK nie może automatycznie wykryć identyfikatora konta usługi. Zamiast tego podczas inicjowania pakietu SDK określ identyfikator konta usługi i upewnij się, że Twoje konto użytkownika ma rolę twórcy tokenów konta usługi na tym koncie usługi.

Jeśli pakiet Firebase Admin SDK musi wykryć ciąg identyfikatora konta usługi, robi to, gdy kod po raz pierwszy tworzy token niestandardowy. Wynik jest zapisywany w pamięci podręcznej i używany ponownie w kolejnych operacjach podpisywania tokenów. Identyfikator automatycznie wykrytego konta usługi to zwykle jedno z domyślnych kont usługi udostępnianych przez Google Cloud:

Podobnie jak w przypadku jawnie określonych identyfikatorów kont usługi, automatycznie wykryte identyfikatory kont usługi muszą mieć uprawnienie iam.serviceAccounts.signBlob, aby tworzenie tokenów niestandardowych działało. Aby przyznać domyślnym kontom usługi niezbędne uprawnienia, może być konieczne użycie sekcji Administracja i uprawnienia w konsoli Google Cloud. Więcej informacji znajdziesz w sekcji dotyczącej rozwiązywania problemów poniżej.

Używanie identyfikatora konta usługi

Aby zachować spójność między różnymi częściami aplikacji, możesz określić identyfikator konta usługi, którego klucze zarządzane przez Google będą używane do podpisywania tokenów. Dzięki temu zasady uprawnień mogą być prostsze i bezpieczniejsze, a Ty nie musisz pobierać, przechowywać ani wdrażać klucza konta usługi.

Identyfikator konta usługi znajdziesz w konsoli Google Cloud. Identyfikatory kont usługi to adresy e-mail w tym formacie: <client-id>@<project-id>.iam.gserviceaccount.com. Jednoznacznie identyfikują konta usługi w projektach Firebase i Google Cloud.

Aby utworzyć tokeny niestandardowe za pomocą osobnego identyfikatora konta usługi, zainicjuj pakiet SDK w sposób pokazany poniżej:

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

Identyfikatory kont usługi nie są informacjami poufnymi, więc ich ujawnienie nie ma znaczenia. Aby jednak podpisywać tokeny niestandardowe za pomocą określonego konta usługi, pakiet Firebase Admin SDK musi wywołać usługę zdalną. Musisz też upewnić się, że tożsamość, której pakiet Admin SDK używa do wykonania tego wywołania (zwykle {project-name}@appspot.gserviceaccount.com w środowisku produkcyjnym lub Twoje własne konto użytkownika podczas programowania lokalnego), ma uprawnienie iam.serviceAccounts.signBlob. Więcej informacji znajdziesz w sekcji dotyczącej rozwiązywania problemów poniżej.

Używanie pliku klucza konta usługi

Jeśli Twój kod działa w środowisku poza Google Cloud, w którym żadna z powyższych metod nie jest dostępna, możesz zainicjować pakiet Admin SDK za pomocą pliku klucza konta usługi. Szczegółowe informacje znajdziesz w instrukcjach konfiguracji pakietu Admin SDK. W przypadku tej metody pakiet Admin SDK podpisuje tokeny niestandardowe lokalnie przy użyciu klucza prywatnego w pliku, bez wykonywania zdalnych wywołań interfejsu API.

Klucze kont usługi to długotrwałe dane uwierzytelniające. Jeśli klucz zostanie ujawniony, każda osoba, która go ma, może działać jako konto usługi, dopóki klucz nie zostanie usunięty. Nigdy nie zatwierdzaj plików kluczy w systemie kontroli wersji i rozważ użycie federacji tożsamości zadań zamiast kluczy, jeśli Twoje środowisko to obsługuje. Pamiętaj też, że niektóre organizacje używają ograniczeń zasad organizacji, aby uniemożliwić tworzenie kluczy kont usługi. W takim przypadku musisz użyć jednej z innych metod.

Tworzenie tokenów niestandardowych za pomocą pakietu Firebase Admin SDK

Pakiet Firebase Admin SDK ma wbudowaną metodę tworzenia tokenów niestandardowych. Musisz podać co najmniej uid, który może być dowolnym ciągiem znaków, ale powinien jednoznacznie identyfikować użytkownika lub urządzenie, które uwierzytelniasz. Te tokeny wygasają po godzinie.

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

Możesz też opcjonalnie określić dodatkowe oświadczenia, które mają być uwzględnione w tokenie niestandardowym. Na przykład poniżej do tokena niestandardowego dodano pole premiumAccount, które będzie dostępne w obiektach auth / request.auth w regułach zabezpieczeń:

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

Zastrzeżone nazwy tokenów niestandardowych

Logowanie się przy użyciu tokenów niestandardowych na klientach

Po utworzeniu tokena niestandardowego wyślij go do aplikacji klienckiej. Aplikacja kliencka uwierzytelnia się za pomocą tokena niestandardowego, wywołując 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;
    // ...
  });

Jeśli uwierzytelnianie się powiedzie, użytkownik zostanie zalogowany w aplikacji klienckiej na konto określone przez uid zawarty w tokenie niestandardowym. Jeśli to konto wcześniej nie istniało, zostanie utworzony rekord dla tego użytkownika.

Podobnie jak w przypadku innych metod logowania (takich jak signInWithEmailAndPassword() i signInWithCredential()) obiekt auth w Realtime Database Security Rules i obiekt request.auth w Cloud Storage Security Rules zostaną wypełnione wartością uid użytkownika. W takim przypadku uid będzie wartością podaną podczas generowania tokena niestandardowego.

Reguły bazy danych

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

Reguły dotyczące miejsca na dane

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

Jeśli token niestandardowy zawiera dodatkowe oświadczenia, można się do nich odwoływać w regułach za pomocą obiektu auth.token (Firebase Realtime Database) lub request.auth.token (Cloud Storage):

Reguły bazy danych

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

Reguły dotyczące miejsca na dane

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

Tworzenie tokenów niestandardowych za pomocą biblioteki JWT innej firmy

Jeśli Twój backend jest w języku, który nie ma oficjalnego pakietu SDK Firebase Admin, nadal możesz ręcznie tworzyć tokeny niestandardowe. Najpierw znajdź bibliotekę JWT innej firmy dla swojego języka. Następnie użyj tej biblioteki JWT, aby utworzyć token JWT zawierający te deklaracje:

Deklaracje tokenów niestandardowych
alg Algorytm "RS256"
iss Wystawca Adres e-mail konta usługi Twojego projektu
sub Temat Adres e-mail konta usługi Twojego projektu
aud Odbiorcy "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat Czas wystawienia Bieżący czas w sekundach od początku epoki uniksowej
exp Data ważności Czas w sekundach od początku epoki uniksowej, po którym token wygasa. Może to być maksymalnie 3600 sekund później niż iat.
Uwaga: to ustawienie określa tylko czas, po którym wygasa sam token niestandardowy. Jednak po zalogowaniu się użytkownika za pomocą signInWithCustomToken() pozostanie on zalogowany na urządzeniu, dopóki jego sesja nie zostanie unieważniona lub dopóki się nie wyloguje.
uid Unikalny identyfikator zalogowanego użytkownika musi być ciągiem znaków o długości od 1 do 128 znaków. Krótsze uid zapewniają lepszą wydajność.
claims (opcjonalnie) Opcjonalne oświadczenia niestandardowe, które mają być uwzględnione w zmiennych auth / request.auth reguł zabezpieczeń

Oto kilka przykładowych implementacji tworzenia tokenów niestandardowych w różnych językach, których nie obsługuje pakiet Firebase Admin SDK. W tych przykładach tokeny są podpisywane lokalnie za pomocą klucza prywatnego konta usługi, więc obowiązują wskazówki dotyczące bezpieczeństwa plików kluczy konta usługi. Aby uniknąć zarządzania kluczem prywatnym, możesz poprosić Google o zdalne podpisanie ładunku tokena przez wywołanie metody signJwt interfejsu Service Account Credentials API. Wywołujący musi mieć rolę Twórca tokenów konta usługi na koncie usługi.

PHP

Korzystanie z php-jwt:

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

// Placeholder values. In production, load your service account's email
// address and private key from a secure secret store. Never hard-code
// private keys in source code.
$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

Korzystanie z ruby-jwt:

require "jwt"

# Placeholder values. In production, load your service account's email
# address and private key from a secure secret store. Never hard-code
# private keys in source code.
$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

Po utworzeniu tokena niestandardowego wyślij go do aplikacji klienckiej, aby użyć go do uwierzytelniania w Firebase. Aby dowiedzieć się, jak to zrobić, zapoznaj się z przykładowym kodem powyżej.

Rozwiązywanie problemów

W tej sekcji opisujemy niektóre typowe problemy, które deweloperzy mogą napotkać podczas tworzenia tokenów niestandardowych, oraz sposoby ich rozwiązywania.

Interfejs IAM API nie jest włączony

Jeśli podasz identyfikator konta usługi do podpisywania tokenów, może pojawić się błąd podobny do tego:

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.

Pakiet Firebase Admin SDK używa interfejsu IAM API do podpisywania tokenów. Ten błąd oznacza, że interfejs IAM API nie jest obecnie włączony w projekcie w Firebase. Otwórz link w komunikacie o błędzie w przeglądarce i kliknij przycisk „Włącz interfejs API”, aby włączyć go w projekcie.

Konto usługi nie ma wymaganych uprawnień

Jeśli konto usługi, na którym działa pakiet Firebase Admin SDK, nie ma uprawnienia iam.serviceAccounts.signBlob, może pojawić się komunikat o błędzie podobny do tego:

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

Aby rozwiązać ten problem, przypisz rolę uprawnień Twórca tokenów konta usługi do odpowiedniego konta usługi. Używane domyślne konto usługi zależy od środowiska i wersji Cloud Functions:

  • Cloud Functions (1 generacji): używa domyślnego konta usługi App Engine.
  • Cloud Functions (2 generacji): używa domyślnego konta usługi Compute Engine.
  1. W konsoli Google Cloud otwórz Uprawnienia i administracja.

  2. Kliknij ikonę edycji odpowiadającą kontu usługi, które chcesz zaktualizować.

  3. Kliknij Dodaj kolejną rolę.

  4. Wpisz "Service Account Token Creator" w filtrze wyszukiwania i wybierz tę usługę z wyników.

  5. Kliknij Zapisz, aby potwierdzić przyznanie roli.

Więcej informacji o tym procesie znajdziesz w dokumentacji uprawnień. Możesz też dowiedzieć się, jak aktualizować role za pomocą narzędzi wiersza poleceń gcloud.

Nie udało się określić konta usługi

Jeśli pojawi się komunikat o błędzie podobny do poniższego, pakiet Firebase Admin SDK nie został prawidłowo zainicjowany.

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

Jeśli korzystasz z pakietu SDK do automatycznego wykrywania identyfikatora konta usługi, upewnij się, że kod jest wdrożony w zarządzanym środowisku Google z serwerem metadanych. W przeciwnym razie pamiętaj, aby podczas inicjowania pakietu SDK podać identyfikator konta usługi lub w środowiskach innych niż Google zainicjować pakiet SDK za pomocą pliku klucza konta usługi.