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 sieciowych JSON (JWT). Wygeneruj te tokeny na swoim serwerze, przekazuj je z powrotem do urządzenia klienckiego, a następnie używasz ich do uwierzytelniania za pomocą metody signInWithCustomToken().

Aby to osiągnąć, musisz utworzyć punkt końcowy serwera, który akceptuje dane logowania, np. nazwę użytkownika i hasło, i zwraca niestandardowy token JWT, jeśli dane logowania są prawidłowe. Niestandardowy token JWT zwrócony z serwera może następnie zostać użyty przez urządzenie klienckie 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 Baza danych czasu rzeczywistego Firebase czy Cloud Storage. Dodatkowo zawartość tokena JWT będzie dostępna w obiekcie auth w regułach zabezpieczeń bazy danych czasu rzeczywistego oraz w obiekcie request.auth w regułach zabezpieczeń Cloud Storage.

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

  • Użycie pliku JSON konta usługi -- Z tej metody można korzystać w dowolnym środowisku, ale wymaga ona spakowania wraz z kodem pliku JSON konta usługi. Zachowaj szczególną ostrożność, aby upewnić się, że plik JSON konta usługi nie jest dostępny dla osób zewnętrznych.
  • Zezwalanie pakietowi Admin SDK na wykrycie konta usługi – tej metody można używać w środowiskach zarządzanych przez Google, takich jak funkcje Google Cloud Functions i App Engine. Konieczne może być skonfigurowanie dodatkowych uprawnień w konsoli Google Cloud.
  • Używanie identyfikatora konta usługi – w przypadku użycia w środowisku zarządzanym przez Google ta metoda będzie podpisywać tokeny przy użyciu klucza określonego konta usługi. Korzysta jednak ze zdalnej usługi sieciowej i może być konieczne skonfigurowanie dodatkowych uprawnień dla tego konta usługi w konsoli Google Cloud.

Za pomocą pliku JSON konta usługi

Pliki JSON konta usługi zawierają wszystkie informacje powiązane z kontami usługi (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 inicjowania jest odpowiednia w przypadku wielu wdrożeń pakietu Admin SDK. Umożliwia też pakietowi Admin SDK tworzenie i podpisywanie tokenów niestandardowych lokalnie, bez konieczności wykonywania zdalnych wywołań interfejsu API. Główną wadą tego sposobu jest to, że wymaga ono spakowania wraz z kodem pliku JSON konta usługi. Pamiętaj też, że klucz prywatny w pliku JSON konta usługi zawiera informacje poufne i musisz zachować ich poufność. W szczególności nie dodawaj plików JSON konta usługi do publicznej kontroli wersji.

Zezwalanie pakietowi Admin SDK na wykrywanie konta usługi

Jeśli Twój kod jest wdrożony w środowisku zarządzanym przez Google, pakiet Admin SDK może automatycznie wykryć sposoby podpisywania tokenów niestandardowych:

  • Jeśli Twój kod jest wdrożony w standardowym środowisku App Engine dla języka Java, Python lub Go, pakiet Admin SDK może podpisywać tokeny niestandardowe przy użyciu usługi tożsamości aplikacji dostępnej w tym środowisku. Usługa App Identity podpisuje dane przy użyciu konta usługi udostępnionego dla aplikacji przez Google App Engine.

  • Jeśli Twój kod jest wdrożony w innym zarządzanym środowisku (np. Google Cloud Functions lub Google Compute Engine), pakiet SDK Firebase Admin 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, pobierz plik JSON konta usługi i ustaw zmienną środowiskową GOOGLE_APPLICATION_CREDENTIALS tak, by wskazywała.

Jeśli pakiet SDK Firebase Admin musi wykryć ciąg identyfikatora konta usługi, zrobi to, gdy Twój kod po raz pierwszy utworzy token niestandardowy. Wynik jest przechowywany w pamięci podręcznej i używany ponownie podczas kolejnych operacji 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 bezpośrednio określonych identyfikatorów kont usługi, automatycznie wykrywane identyfikatory kont usługi muszą mieć uprawnienie iam.serviceAccounts.signBlob, aby można było tworzyć tokeny niestandardowych. Konieczne może być użycie sekcji Administracja w konsoli Google Cloud, aby przyznać domyślnym kontom usługi niezbędne uprawnienia. 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 działania w środowisku zarządzanym przez Google. Może to uprościć i zwiększyć bezpieczeństwo zasad uprawnień, a także uniknąć konieczności umieszczania pliku JSON konta usługi w kodzie.

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

Aby utworzyć tokeny niestandardowe z użyciem osobnego identyfikatora konta usługi, zainicjuj pakiet SDK w następujący 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, więc ich ujawnienie nie ma znaczenia. Jednak aby podpisać tokeny niestandardowe za pomocą określonego konta usługi, pakiet SDK Firebase Admin musi wywołać usługę zdalną. Musisz też upewnić się, że konto usługi używane przez pakiet Admin SDK do wywoływania tego wywołania (zwykle {project-name}@appspot.gserviceaccount.com) ma uprawnienie iam.serviceAccounts.signBlob. Więcej informacji znajdziesz w sekcji rozwiązywania problemów poniżej.

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

Pakiet SDK Firebase Admin 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, dla których uwierzytelniasz się. Tokeny tracą ważność 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 żądania do uwzględnienia w tokenie niestandardowym. Poniżej widać na przykład, że do tokena niestandardowego, które będzie dostępne w obiektach auth / request.auth w regułach zabezpieczeń, dodaliśmy pole premiumAccount:

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 się przy użyciu tokenów niestandardowych na kontach klientów

Po utworzeniu tokena niestandardowego wyślij go do aplikacji klienckiej. Aplikacja kliencka uwierzytelnia się za pomocą tokena niestandardowego, wywołując metodę 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 niestandardowym tokenie. Jeśli dane konto nie istniało wcześniej, zostanie dla niego utworzony rekord.

Tak samo jak w przypadku innych metod logowania (takich jak signInWithEmailAndPassword() i signInWithCredential()), obiekt auth w regułach zabezpieczeń bazy danych czasu rzeczywistego i obiekt request.auth w regułach zabezpieczeń Cloud Storage zostaną uzupełnione wartością uid użytkownika. W tym przypadku uid będzie tym, który został podany podczas generowania tokena niestandardowego.

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 token niestandardowy zawiera dodatkowe żądania, w regułach można się odwoływać z obiektu auth.token (Baza danych czasu rzeczywistego Firebase) lub request.auth.token (Cloud Storage):

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 tokenów niestandardowych za pomocą biblioteki JWT innej firmy

Jeśli Twój backend obsługuje język, który nie ma oficjalnego pakietu SDK Firebase Admin, możesz ręcznie tworzyć tokeny niestandardowe. Najpierw znajdź zewnętrzną bibliotekę JWT dla swojego języka. Następnie użyj biblioteki JWT, aby utworzyć token JWT z tymi stwierdzeniami:

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 Wystawiony w momencie Bieżący czas (w sekundach) od początku epoki UNIX
exp Okres ważności Czas (w sekundach) od początku epoki systemu UNIX, w którym wygasa token. Może to być maksymalnie 3600 sekund później niż iat.
Uwaga: ta opcja określa tylko czas wygaśnięcia tokena niestandardowego. Jednak po zalogowaniu użytkownika przy użyciu signInWithCustomToken() pozostaje on zalogowany na urządzeniu do czasu unieważnienia jego sesji lub wylogowania się użytkownika.
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 uid zapewniają większą skuteczność.
claims (opcjonalnie) Opcjonalne żądania niestandardowe, które mają być uwzględnione w zmiennych Reguły zabezpieczeń auth / request.auth

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

PHP

Przy użyciu 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

Przy użyciu 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 mógł go używać do uwierzytelniania w Firebase. Aby dowiedzieć się, jak to zrobić, zapoznaj się z przykładami powyżej.

Rozwiązywanie problemów

W tej sekcji omawiamy kilka częstych problemów, które mogą napotkać programiści podczas tworzenia tokenów niestandardowych, oraz sposoby ich rozwiązywania.

Nie włączono interfejsu IAM API

Jeśli określisz identyfikator konta usługi na potrzeby tokenów podpisywania, 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 SDK Firebase Admin używa do podpisywania tokenów interfejsu IAM API. Ten błąd oznacza, że interfejs IAM API nie jest obecnie włączony w Twoim projekcie Firebase. Otwórz w przeglądarce link w komunikacie o błędzie i kliknij przycisk „Włącz interfejs API”, aby włączyć ten interfejs API w projekcie.

Konto usługi nie ma wymaganych uprawnień

Jeśli konto usługi, z którego działa pakiet Firebase Admin SDK, nie ma uprawnień iam.serviceAccounts.signBlob, może pojawić się następujący komunikat o błędzie:

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

Najłatwiejszym sposobem rozwiązania tego problemu jest przypisanie roli uprawnień „Twórca tokenów konta usługi” do odpowiedniego konta usługi (zwykle {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 „Service Account Token Creator” (Twórca tokenów konta usługi) w filtrze wyszukiwania i wybierz go z wyników.
  6. Kliknij „Zapisz”, aby potwierdzić przypisanie roli.

Więcej informacji o tym procesie znajdziesz w dokumentacji uprawnień. Możesz się też dowiedzieć, 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 poniżej, oznacza to, że 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 używasz pakietu SDK do automatycznego wykrywania identyfikatora konta usługi, upewnij się, że kod został 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 konta usługi.