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). Tokeny te generujesz na serwerze, przekazujesz je z powrotem na urządzenie klienta, a potem używasz ich do uwierzytelniania za pomocą metody signInWithCustomToken().

Aby to zrobić, musisz utworzyć punkt końcowy serwera, który akceptuje dane logowania (np. nazwę 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 się w Firebase (iOS+, Android, web). 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 auth w Twoim Realtime Database Security Rules i w obiekcie request.auth w Twoim Cloud Storage Security Rules.

Możesz utworzyć niestandardowy token za pomocą pakietu Firebase Admin SDK lub użyć biblioteki JWT innej firmy, 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 na określenie konta usługi Google, którego pakiet SDK Firebase Admin powinien używać 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 razem z kodem. Należy zachować szczególną ostrożność, aby plik JSON konta usługi nie był dostępny dla osób z zewnątrz.
  • Zezwalanie pakietowi Admin SDK na wykrywanie 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żywanie identyfikatora konta usługi – w środowisku zarządzanym przez Google ta metoda będzie podpisywać tokeny za pomocą klucza określonego konta usługi. Używa ona jednak zdalnej usługi internetowej, więc może być konieczne skonfigurowanie dodatkowych uprawnień dla tego konta usługi za pomocą konsoli Google Cloud.

Korzystanie z pliku JSON konta usługi

Pliki JSON kont usług zawierają wszystkie informacje odpowiadające kontom usług (w tym klucz prywatny RSA). Można je pobrać z konsoli Firebase. Aby dowiedzieć się więcej o inicjowaniu pakietu Admin SDK za pomocą pliku JSON konta usługi, postępuj zgodnie z instrukcjami konfiguracji pakietu Admin SDK.

Ta metoda inicjalizacji jest odpowiednia do wielu wdrożeń pakietu Admin SDK. Umożliwia też pakietowi Admin SDK tworzenie i podpisywanie tokenów niestandardowych lokalnie, bez wykonywania żadnych wywołań interfejsu API zdalnie. Główną wadą tego podejścia jest to, że wymaga spakowania pliku JSON konta usługi wraz z kodem. Pamiętaj też, że klucz prywatny w pliku JSON konta usługi to informacje poufne, które należy chronić przed ujawnieniem. W szczególności nie dodawaj plików JSON konta usługi do publicznego systemu kontroli wersji.

Pozwalanie pakietowi Admin SDK na wykrywanie konta usługi

Jeśli Twój kod jest wdrożony w środowisku zarządzanym przez Google, pakiet SDK do zarządzania może automatycznie próbować wykryć sposób podpisywania tokenów niestandardowych:

  • Jeśli kod jest wdrożony w standardowym środowisku App Engine dla Javy, Pythona lub Go, pakiet Admin SDK może korzystać z usługi tożsamości aplikacji dostępnej w tym środowisku, aby podpisywać tokeny niestandardowe. Usługa AppIdentity podpisuje dane za pomocą konta usługi udostępnionego Twojej aplikacji przez Google App Engine.

  • Jeśli Twój kod jest wdrożony w innym zarządzanym środowisku (np. w Google Cloud Functions lub Google Compute Engine), pakiet SDK Firebase Admin może automatycznie wykryć ciąg znaków identyfikatora konta usługi z lokalnego serwera metadanych. Odkryte identyfikatory kont usługi są następnie używane w połączeniu z usługą IAM do podpisywania tokenów zdalnie.

Aby korzystać z tych metod podpisywania, zainicjuj pakiet SDK przy użyciu domyślnych danych logowania Google i nie określaj 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, pobierz plik JSON konta usługi i ustaw na niego zmienną środowiskową GOOGLE_APPLICATION_CREDENTIALS.

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

Podobnie jak w przypadku jawnie podanych identyfikatorów kont usługi, identyfikatory kont usługi wykryte automatycznie muszą mieć uprawnienia iam.serviceAccounts.signBlob, aby można było tworzyć tokeny niestandardowe. Aby przyznać domyślnym kontom usługi niezbędne uprawnienia, konieczne może być użycie sekcji Uprawnienia i zarządzanie w konsoli Google Cloud. Więcej informacji znajdziesz w sekcji Rozwiązywanie problemów.

Korzystanie z 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 będą prostsze i bezpieczniejsze, a Ty nie będziesz musiał uwzględniać w kodzie pliku JSON konta usługi.

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

Aby utworzyć tokeny niestandardowe przy użyciu osobnego identyfikatora konta usługi, zainicjuj SDK w ten sposób:

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, dlatego 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ę zdalnego dostępu. Musisz też sprawdzić, czy konto usługi używane przez pakiet Admin SDK do wykonywania tego wywołania (zazwyczaj {project-name}@appspot.gserviceaccount.com) ma uprawnienia iam.serviceAccounts.signBlob. Więcej informacji znajdziesz w sekcji Rozwiązywanie problemów.

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

Pakiet Admin SDK Firebase zawiera wbudowaną metodę tworzenia niestandardowych tokenów. Musisz podać co najmniej uid, który może być dowolnym ciągiem znaków, ale musi jednoznacznie identyfikować użytkownika lub urządzenie, które chcesz uwierzytelnić. 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

Opcjonalnie możesz też określić dodatkowe oświadczenia, które mają być uwzględnione w tokenie niestandardowym. Na przykład poniżej dodaliśmy do niestandardowego elementu tokena pole premiumAccount, które będzie dostępne w obiektach auth / request.auth w Twoich 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 niestandardowych tokenów

Logowanie się za pomocą tokenów niestandardowych w klientach

Po utworzeniu tokena niestandardowego wyślij go do aplikacji klienta. Aplikacja klienta 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 zaloguje się w aplikacji klienta za pomocą konta określonego przez uid zawarte w tokenie niestandardowym. Jeśli konto nie istniało wcześniej, zostanie utworzony rekord dla tego użytkownika.

Podobnie jak w przypadku innych metod logowania (takich jak signInWithEmailAndPassword()signInWithCredential()), obiekt auth w elementach Realtime Database Security Rulesrequest.auth w elementach Cloud Storage Security Rules zostanie wypełniony wartością uid użytkownika. W tym przypadku parametr uid będzie miał wartość określoną podczas generowania tokenu niestandardowego.

Reguły bazy danych

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

Storage Rules

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 na nie się powoływać w obiekcie auth.token (Firebase Realtime Database) lub request.auth.token (Cloud Storage) w swoich regułach:

Reguły bazy danych

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

Storage Rules

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ą zewnętrznej biblioteki JWT

Jeśli backend jest w języku, w którym nie ma oficjalnego pakietu SDK Firebase Admin, możesz ręcznie utworzyć tokeny niestandardowe. Najpierw znajdź bibliotekę JWT innej firmy w odpowiednim języku. Następnie użyj tej biblioteki JWT, aby utworzyć token JWT, który zawiera te oświadczenia:

Deklaracje tokenów niestandardowych
alg Algorytm "RS256"
iss Wystawca Adres e-mail konta usługi projektu
sub Temat Adres e-mail konta usługi projektu
aud Odbiorcy "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat Issued-at time Aktualny czas w sekundach od początku epoki UNIX
exp Okres ważności Czas w sekundach od początku epoki UNIX, w którym token traci ważność. Może ona nastąpić maksymalnie 3600 sekund później niż iat.
Uwaga: to ustawienie określa tylko czas wygaśnięcia tokenu niestandardowego. Jednak po zalogowaniu użytkownika za pomocą opcji signInWithCustomToken() pozostanie on zalogowany na urządzeniu do czasu unieważnienia sesji lub wylogowania.
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 do uwzględnienia w regułach bezpieczeństwa auth / request.auth

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

PHP

Używając 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

Używając 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 tokena niestandardowego wyślij go do aplikacji klienckiej, aby używać go do uwierzytelniania w Firebase. Aby dowiedzieć się, jak to zrobić, zapoznaj się z przykładowymi fragmentami kodu powyżej.

Rozwiązywanie problemów

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

Interfejs API IAM nie jest włączony

Jeśli podasz identyfikator konta usługi do podpisywania tokenów, możesz zobaczyć 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 uprawnień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”. Zazwyczaj:{project-name}@appspot.gserviceaccount.com

  1. Otwórz stronę Administracja w konsoli Google Cloud.
  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 „Twórca tokenów konta usługi” w filtrze wyszukiwania i wybierz go z wyników.
  6. Aby potwierdzić przyznanie roli, kliknij „Zapisz”.

Więcej informacji o tym procesie znajdziesz w dokumentacji dotyczącej uprawnień. Możesz też dowiedzieć się, jak zaktualizować 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, 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 podczas inicjowania pakietu SDK podaj plik JSON konta usługi lub identyfikator tego konta.