Tworzenie tokenów niestandardowych

Firebase zapewnia pełną kontrolę nad uwierzytelnianiem, umożliwiając: uwierzytelniać użytkowników lub urządzenia przy użyciu bezpiecznych tokenów sieciowych JSON (JWT). Generujesz te tokeny na serwerze, przekaż je z powrotem do urządzenia klienckiego, a następnie użyj ich uwierzytelnienie 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 z serwera może wtedy być używane 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 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ć token niestandardowy za pomocą pakietu Firebase Admin SDK lub użyj zewnętrznej biblioteki JWT, jeśli Twój serwer jest zapisany w Język, 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. Zachowaj szczególną ostrożność, aby sprawdzić, czy Plik JSON konta usługi nie jest dostępny dla osób zewnętrznych.
  • 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. Konieczne może być 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. Korzysta jednak ze zdalnej usługi sieciowej i może być konieczne skonfigurowanie dodatkowych uprawnień dla tego konta usługi za pośrednictwem Konsola Google Cloud.

Korzystanie z pliku JSON konta usługi

Pliki JSON konta usługi zawierają wszystkie informacje powiązane z usługą (w tym klucz prywatny RSA). Można je pobrać z konsoli Firebase. Postępuj zgodnie z instrukcjami konfiguracji pakietu Admin SDK , gdzie znajdziesz więcej informacji o tym, zainicjować pakiet Admin SDK plikiem JSON konta usługi.

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 konta usługi Plik JSON zawiera informacje poufne i należy zachować szczególną ostrożność, aby je poufne. 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 Admin SDK może spróbować automatycznie wykryć sposób podpisywania tokenów niestandardowych:

  • Jeśli Twój kod jest wdrożony w standardowym środowisku App Engine w usłudze Java, Python lub Go, pakiet Admin SDK może używać Usługa tożsamości aplikacji dostępnych w tym środowisku do podpisywania tokenów niestandardowych. 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. Wykryty identyfikator konta usługi jest następnie używany w połączeniu z uprawnieniami. do zdalnego podpisywania tokenów.

Aby korzystać z tych metod podpisywania, zainicjuj pakiet SDK w Google Domyślne dane logowania aplikacji 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 Zmienna środowiskowa GOOGLE_APPLICATION_CREDENTIALS wskazująca ją.

Jeśli pakiet SDK Firebase Admin musi wykryć ciąg identyfikatora konta usługi, więc gdy kod tworzy token niestandardowy po raz pierwszy. 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 określonych 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 IAM i administracja w konsoli Google Cloud. Więcej informacji znajdziesz w sekcji Rozwiązywanie problemów.

Używanie identyfikatora konta usługi

Aby zachować spójność między różnymi częściami aplikacji, możesz określ 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ługi to adresy e-mail w 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, więc ich ujawnienie jest bez znaczenia. Jednak aby podpisywać tokeny niestandardowe za pomocą określonej usługi pakiet SDK Firebase Admin musi wywołać usługę zdalną. Upewnij się też, że konto usługi, do którego jest używany pakiet Admin SDK, użyj, aby nawiązać to połączenie – zwykle {project-name}@appspot.gserviceaccount.com– ma iam.serviceAccounts.signBlob uprawnienia. Więcej informacji znajdziesz w sekcji Rozwiązywanie problemów.

Tworzenie niestandardowych tokenów 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 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. Przykładowo poniżej dodano pole premiumAccount do token niestandardowy, który będzie dostępny 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 niestandardowych tokenów

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

Po utworzeniu tokena niestandardowego wyślij go do aplikacji klienckiej. aplikacja kliencka uwierzytelnia się za pomocą tokena niestandardowego przez wywołanie 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 na aplikacja kliencka z kontem określonym w polu uid uwzględnionym w niestandardowym token. Jeśli konto nie istniało wcześniej, zostanie utworzony rekord dla tego użytkownika.

Tak samo jak w przypadku innych metod logowania (np. signInWithEmailAndPassword() i signInWithCredential()) obiekt auth w Realtime Database Security Rules oraz obiekt request.auth w Cloud Storage Security Rules będzie zawiera wartość 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 je wywołać z obiektu 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"
    }
  }
}

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 backend jest w języku, który nie ma oficjalnego administratora Firebase SDK, nadal możesz ręcznie tworzyć tokeny niestandardowe. Najpierw znajdź bibliotekę JWT innej firmy w odpowiednim języku. Następnie użyj funkcji tę bibliotekę JWT do utworzenia tokena JWT, który zawiera następujące informacje:

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 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 ona nastąpić maksymalnie 3600 sekund później niż iat.
Uwaga: to ustawienie określa tylko czas wygaśnięcia tokenu niestandardowego. Gdy jednak zalogujesz się za pomocą signInWithCustomToken(), pozostanie zalogowany(-a) w aplikacji do czasu unieważnienia 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. 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 tokenów niestandardowych w różne języki, 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 używać go do uwierzytelniania w Firebase. Aby dowiedzieć się, jak to zrobić, zapoznaj się z przykładami 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 IAM API nie jest włączony

Jeśli określasz identyfikator konta usługi na potrzeby tokenów podpisywania, możesz otrzymać podobny błąd:

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 dla projekt 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 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”.

Zapoznaj się z dokumentacją uprawnień. . w narzędziach 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 podaj plik JSON konta usługi lub identyfikator konta usługi podczas inicjowania pakietu SDK.