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 na urządzenie 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, a jeśli są one prawidłowe, zwraca niestandardowy token JWT. Niestandardowy token JWT zwrócony przez serwer może być następnie używany przez urządzenie klienta do uwierzytelniania w Firebase (iOS+, Android, internet). Po uwierzytelnieniu ta tożsamość będzie używana podczas uzyskiwania dostępu do innych usług Firebase, takich jak Firebase Realtime DatabaseCloud Storage. Ponadto zawartość tokena JWT będzie dostępna w obiekcie authRealtime Database Security Rules i w obiekcie request.authCloud Storage Security Rules.

Możesz utworzyć niestandardowy token za pomocą pakietu Firebase Admin SDK lub użyć biblioteki JWT innej firmy, jeśli 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żyty do podpisywania należy do konta usługi Google. Istnieje kilka sposobów określania konta usługi Google, które powinno być używane przez pakiet Firebase Admin SDK do podpisywania niestandardowych tokenów:

  • Używanie pliku JSON konta usługi – tej metody można używać w dowolnym środowisku, ale wymaga ona spakowania pliku JSON konta usługi wraz z kodem. Należy zachować szczególną ostrożność, aby plik JSON konta usługi nie był dostępny dla podmiotów zewnętrznych.
  • Umożliwianie pakietowi Admin SDK wykrywania konta usługi – ta metoda może być używana 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żywanie identyfikatora konta usługi – w środowisku zarządzanym przez Google ta metoda podpisuje tokeny za pomocą klucza określonego konta usługi. Korzysta ona jednak z usługi sieciowej, więc może być konieczne skonfigurowanie dodatkowych uprawnień dla tego konta usługi w konsoli Google Cloud.

Używanie pliku JSON konta usługi

Pliki JSON kont usługi zawierają wszystkie informacje dotyczące kont usługi (w tym klucz prywatny RSA). Można je pobrać z konsoli Firebase. Więcej informacji o inicjowaniu pakietu Admin SDK za pomocą pliku JSON konta usługi znajdziesz w instrukcjach konfiguracji pakietu Admin SDK.

Ta metoda inicjowania jest odpowiednia w przypadku wielu wdrożeń pakietu Admin SDK. Umożliwia też pakietowi Admin SDK tworzenie i podpisywanie lokalnie tokenów niestandardowych bez wykonywania zdalnych wywołań interfejsu API. Główną wadą tego podejścia jest to, że wymaga ono spakowania pliku JSON konta usługi wraz z kodem. Pamiętaj też, że klucz prywatny w pliku JSON konta usługi to informacje poufne, dlatego należy zachować szczególną ostrożność, aby nie ujawnić go osobom nieupoważnionym. W szczególności nie dodawaj plików JSON konta usługi do publicznej kontroli wersji.

Umożliwianie pakietowi Admin SDK wykrywania konta usługi

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

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

  • Jeśli Twój 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ą IAM do zdalnego podpisywania tokenów.

Aby korzystać z tych metod podpisywania, zainicjuj pakiet SDK za pomocą domyślnych danych logowania aplikacji Google i nie podawaj ciągu znaków 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, pobierz plik JSON konta usługi i ustaw zmienną środowiskową GOOGLE_APPLICATION_CREDENTIALS tak, aby wskazywała ten plik.

Jeśli pakiet Firebase Admin SDK musi wykryć ciąg identyfikatora konta usługi, robi to, gdy Twój kod po raz pierwszy tworzy niestandardowy token. Wynik jest buforowany i ponownie wykorzystywany w przypadku kolejnych operacji podpisywania tokenów. Automatycznie wykryty identyfikator konta usługi jest zwykle jednym 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 można było utworzyć token niestandardowy. Może być konieczne przyznanie domyślnym kontom usługi niezbędnych uprawnień w sekcji IAM i administracja w konsoli Google Cloud. Więcej informacji znajdziesz w sekcji 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 będą używane do podpisywania tokenów podczas działania w środowisku zarządzanym przez Google. Dzięki temu zasady IAM mogą być prostsze i bezpieczniejsze, a w kodzie nie trzeba uwzględniać pliku JSON konta usługi.

Identyfikator konta usługi znajdziesz w Google Cloudkonsoli lub w polu client_email pobranego pliku JSON konta usługi. Identyfikatory kont usługi to adresy e-mail w tym formacie:<client-id>@<project-id>.iam.gserviceaccount.com jednoznacznie identyfikują konta usługi w Firebase i Google Cloud projektach;

Aby utworzyć niestandardowe tokeny przy użyciu 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 podpisać niestandardowe tokeny za pomocą określonego konta usługi, pakiet Firebase Admin SDK musi wywołać usługę zdalną. Musisz też upewnić się, że konto usługi, którego pakiet Admin SDK używa do wykonania tego wywołania – zwykle {project-name}@appspot.gserviceaccount.com – ma iam.serviceAccounts.signBlob uprawnienia. Więcej informacji znajdziesz w sekcji rozwiązywania problemów poniżej.

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

Pakiet Firebase Admin SDK ma wbudowaną metodę tworzenia niestandardowych tokenów. Musisz podać co najmniej uid, czyli dowolny ciąg znaków, który powinien jednoznacznie identyfikować uwierzytelnianego użytkownika lub urządzenie. 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 roszczenia, które mają być uwzględnione w niestandardowym tokenie. Na przykład poniżej do niestandardowego tokena 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

Zarezerwowane nazwy tokenów niestandardowych

Logowanie za pomocą niestandardowych tokenów na klientach

Po utworzeniu niestandardowego tokena wyślij go do aplikacji klienckiej. Aplikacja kliencka uwierzytelnia się za pomocą niestandardowego tokena, wywołując funkcję 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 zaloguje się w aplikacji klienckiej za pomocą konta określonego przez parametr uid zawarty w niestandardowym tokenie. Jeśli to konto nie istniało wcześniej, zostanie utworzony rekord tego użytkownika.

Podobnie jak w przypadku innych metod logowania (np. signInWithEmailAndPassword()signInWithCredential()) obiekt authRealtime Database Security Rules i obiekt request.authCloud Storage Security Rules będą wypełnione uid użytkownika. W tym przypadku uid będzie wartością określoną podczas generowania niestandardowego tokena.

Reguły bazy danych

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

Reguły przechowywania

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 niestandardowy token zawiera dodatkowe roszczenia, można się do nich odwoływać za pomocą obiektu auth.token (Firebase Realtime Database) lub request.auth.token (Cloud Storage) w regułach:

Reguły bazy danych

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

Reguły przechowywania

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

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

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

Deklaracje tokenów niestandardowych
alg Algorytm "RS256"
iss Wystawca Adres e-mail konta usługi w projekcie
sub Temat Adres e-mail konta usługi w projekcie
aud Odbiorcy "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat Godzina wydania Bieżący czas w sekundach od początku epoki systemu UNIX.
exp Okres ważności Czas wygaśnięcia tokena podany w sekundach od początku epoki systemu UNIX. Może być maksymalnie o 3600 sekund późniejsza niż wartość iat.
Uwaga: to ustawienie kontroluje tylko czas wygaśnięcia samego tokena niestandardowego. Gdy jednak zalogujesz użytkownika za pomocą signInWithCustomToken(), pozostanie on zalogowany na urządzeniu, dopóki jego sesja nie straci ważności lub użytkownik 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 (włącznie). Krótsze uids zapewniają lepsze wyniki.
claims (opcjonalnie) Opcjonalne niestandardowe roszczenia, które mają być uwzględnione w zmiennych reguł zabezpieczeń auth / request.auth

Oto przykłady implementacji tworzenia niestandardowych tokenów w różnych językach, których nie obsługuje pakiet Firebase Admin SDK:

PHP

Korzystanie z 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

Korzystanie z 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

Po utworzeniu niestandardowego tokena wyślij go do aplikacji klienta, aby użyć go do uwierzytelniania w Firebase. Sposób, w jaki to zrobić, znajdziesz w przykładowych fragmentach kodu powyżej.

Rozwiązywanie problemów

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

Interfejs IAM API nie jest włączony

Jeśli podajesz 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 Twoim projekcie 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}.

Najprostszym sposobem rozwiązania tego problemu jest przypisanie do danego konta usługi roli uprawnień „Twórca tokenów konta usługi”, zwykle {project-name}@appspot.gserviceaccount.com:

  1. W konsoli Google Cloud otwórz stronę Administracja.
  2. Wybierz projekt i kliknij „Dalej”.
  3. Kliknij ikonę edycji odpowiadającą kontu usługi, które chcesz zaktualizować.
  4. Kliknij „Dodaj kolejną rolę”.
  5. Wpisz w filtrze wyszukiwania „Twórca tokenów konta usługi” i wybierz go z wyników.
  6. Aby potwierdzić przyznanie roli, kliknij „Zapisz”.

Więcej informacji o tym procesie znajdziesz w dokumentacji IAM. 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 tego poniżej, 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 wdrażany w zarządzanym środowisku Google z serwerem metadanych. W przeciwnym razie podczas inicjowania pakietu SDK podaj plik JSON konta usługi lub identyfikator konta usługi.