建立自訂權杖

Firebase 可讓您使用安全的 JSON Web Token (JWT) 驗證使用者或裝置,全面掌控驗證作業。您可以在伺服器上產生這些權杖,將其傳回用戶端裝置,然後透過 signInWithCustomToken() 方法使用這些權杖進行驗證。

為此,您必須建立可接受登入憑證 (例如使用者名稱和密碼) 的伺服器端點,並在憑證有效時傳回自訂 JWT。接著,從伺服器傳回的自訂 JWT 就能 用戶端裝置會使用這個金鑰向 Firebase 進行驗證 (iOS+Android網頁)。驗證完畢後,這個身分就會 存取其他 Firebase 服務 (例如 Firebase Realtime Database) 時使用的 和 Cloud Storage。此外,JWT 的內容 可在應用程式的 auth 物件中使用 Realtime Database Security Rulesrequest.auth物件 Cloud Storage Security Rules

您可以使用 Firebase Admin SDK 建立自訂權杖,或是 如果您的伺服器是以 也就是 Firebase 不支援的語言

事前準備

自訂權杖是已簽署的 JWT,且用來簽署的私密金鑰所屬的金鑰為該 JWT Google 服務帳戶您可以透過多種方式指定 Firebase Admin SDK 應使用的 Google 服務帳戶,以便簽署自訂權杖:

  • 使用服務帳戶 JSON 檔案:這個方法可在任何環境中使用,但需要將服務帳戶 JSON 檔案與程式碼一併封裝。請特別注意,確保服務帳戶 JSON 檔案不會外洩給外部人士。
  • 讓 Admin SDK 探索服務帳戶:這個方法可用於 Google 管理的環境,例如 Google Cloud Functions 和 App Engine。您可能需要設定一些 以Google Cloud控制台授予其他權限
  • 使用服務帳戶 ID:在 Google 管理的環境中使用時,這個方法會使用指定服務帳戶的金鑰簽署權杖。不過,由於這個服務帳戶會使用遠端 Web 服務,您可能需要透過 Google Cloud 主控台為該服務帳戶設定其他權限。

使用服務帳戶 JSON 檔案

服務帳戶 JSON 檔案內含與服務相關的所有資訊 帳戶 (包括 RSA 私密金鑰)。您可以從 Firebase 控制台下載這些檔案。如要進一步瞭解如何使用服務帳戶 JSON 檔案初始化 Admin SDK,請按照Admin SDK 設定操作說明操作。

這種初始化方法適用於各種 Admin SDK Deployment 規格此外,這項功能還可讓 Admin SDK 在本機建立及簽署自訂權杖,而無需進行任何遠端 API 呼叫。 您需要封裝服務帳戶 JSON 檔案 和程式碼另請注意,服務帳戶中的私密金鑰 JSON 檔案是機密資訊,請特別留意以保留 機密資料。具體來說,請勿將服務帳戶 JSON 檔案新增至公開版本控制。

讓 Admin SDK 探索服務帳戶

如果您的程式碼部署在 Google 管理的環境中,Admin SDK 會嘗試自動探索簽署自訂權杖的方法:

  • 如果您的程式碼是針對 Java、Python 或 Go 部署至 App Engine 標準環境,Admin SDK 就能使用該環境中的 App Identity 服務簽署自訂權杖。應用程式身分識別服務 透過 Google App 為您的應用程式佈建的服務帳戶簽署資料 Compute Engine

  • 如果您的程式碼部署在其他受管理的環境中 (例如 Google Cloud Functions、Google Compute Engine),Firebase Admin SDK 就能從本機中繼資料伺服器中自動探索服務帳戶 ID 字串。系統會將所偵測到的服務帳戶 ID 與 IAM 服務搭配使用,以便遠端簽署權杖。

如要使用這些簽署方法,請透過 Google 應用程式預設憑證,而且未指定服務帳戶 ID 字串:

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

如要在本機測試相同程式碼,請下載服務帳戶 JSON 檔案,然後設定 GOOGLE_APPLICATION_CREDENTIALS 環境變數指向該變數。

如果 Firebase Admin SDK 必須探索服務帳戶 ID 字串,則會在您的程式碼首次建立自訂權杖時進行。系統會將結果快取,並在後續的權杖簽署作業中重複使用。自動偵測到的服務帳戶 ID 通常是 Google Cloud 提供的預設服務帳戶之一:

就像明確指定的服務帳戶 ID 一樣,自動探索的服務帳戶 ID 必須具備 iam.serviceAccounts.signBlob 權限,才能建立自訂憑證。您可能需要使用 Google Cloud 主控台的「IAM 與管理員」部分,將必要權限授予預設服務帳戶。詳情請參閱下方的疑難排解部分。

使用服務帳戶 ID

如要維持應用程式各部分之間的一致性,您可以 指定要在執行期間用於簽署權杖的服務帳戶 ID 部署容器 這可簡化 IAM 政策,並為您提高安全性,同時 在程式碼中加入服務帳戶 JSON 檔案

您可以在 Google Cloud 控制台, 或下載服務帳戶 JSON 檔案的 client_email 欄位中。 服務帳戶 ID 是採用下列格式的電子郵件地址: <client-id>@<project-id>.iam.gserviceaccount.com。這些 ID 可用於在 Firebase 和 Google Cloud 專案中識別服務帳戶。

如要使用不同的服務帳戶 ID 建立自訂權杖,請按照下列方式初始化 SDK:

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

服務帳戶 ID 不是機密資訊,因此會遭到外洩 依序進行不過,如要使用指定的服務帳戶簽署自訂符記,Firebase Admin SDK 必須叫用遠端服務。此外,您也必須確定 Admin SDK 所在的服務帳戶 用於撥打這通電話 —通常為 {project-name}@appspot.gserviceaccount.com— 擁有iam.serviceAccounts.signBlob 權限。 詳情請參閱下方的疑難排解部分。

使用 Firebase Admin SDK 建立自訂權杖

Firebase Admin SDK 內建可建立自訂權杖的方法。您至少需要提供 uid,可以是任何字串,但應能明確識別您要驗證的使用者或裝置。這些權杖有效期限 一小時後

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

您也可以選擇指定要納入自訂標籤的其他著作權聲明 產生下一個符記舉例來說,下方範例新增了 premiumAccount 欄位 自訂權杖,您可以在 auth / request.auth 物件中找到 在您的安全性規則中執行以下操作:

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

保留自訂權杖名稱

在用戶端使用自訂權杖登入

建立自訂權杖後,請將其傳送至用戶端應用程式。 呼叫用戶端應用程式,以自訂權杖進行驗證 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;
    // ...
  });

如果驗證成功,使用者現在就能使用自訂權杖中 uid 指定的帳戶登入您的用戶端應用程式。如果該帳戶先前不存在,系統就會建立該使用者的記錄。

與其他登入方法 (例如 signInWithEmailAndPassword()signInWithCredential()) 相同,Realtime Database Security Rules 中的 auth 物件和 Cloud Storage Security Rules 中的 request.auth 物件都會填入使用者的 uid。在此情況下,uid 將會是 您在產生自訂符記時指定的組合

資料庫規則

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

儲存空間規則

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

如果自訂權杖包含額外憑證附加資訊,就能參照這些憑證附加資訊, auth.token (Firebase Realtime Database) 或request.auth.token 規則中的 (Cloud Storage) 物件:

資料庫規則

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

儲存空間規則

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

使用第三方 JWT 程式庫建立自訂權杖

如果後端使用的是官方 Firebase Admin SDK 不支援的語言,您仍可手動建立自訂符記。首先,找出適用於您語言的第三方 JWT 程式庫。接著,使用該 JWT 程式庫鑄造 JWT,其中包含下列宣告:

自訂權杖憑證附加資訊
alg 演算法 "RS256"
iss 核發單位 專案的服務帳戶電子郵件地址
sub 主旨 專案的服務帳戶電子郵件地址
aud 目標對象 "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat 核發時間 目前時間 (從 UNIX 紀元起算,以秒為單位)
exp 到期時間 權杖到期時間,以自 Unix 紀元時間起算的秒數為單位。比 iat最多 3600 秒
注意:這只會控制自訂權杖本身的時間 過期。不過,一旦您使用 signInWithCustomToken() 為使用者登入,他們就會持續登入裝置,直到工作階段失效或使用者登出為止。
uid 已登入使用者的專屬 ID 必須是字串,位於 長度介於 1 到 128 個字元之間 (含首尾)。較短的 uid 可提供更佳效能。
claims (非必要) 要加入安全性規則 auth 的選用自訂憑證附加資訊 / request.auth 個變數

以下實作範例說明如何在 Firebase Admin SDK 不支援下列多種語言:

PHP

使用 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

使用 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

建立自訂權杖後,請將其傳送至用戶端應用程式,以便用於透過 Firebase 進行驗證。操作方法請參閱上方的程式碼範例。

疑難排解

本節概略說明開發人員在建立自訂符記時可能遇到的常見問題,以及解決方法。

未啟用 IAM API

如果您為簽署權杖指定服務帳戶 ID 顯示類似下列內容的錯誤:

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.

Firebase Admin SDK 會使用 IAM API 簽署權杖。這個錯誤表示您的 Firebase 專案目前未啟用 IAM API。在網路瀏覽器中開啟錯誤訊息中的連結,然後按一下「Enable API」(啟用 API) 按鈕,為專案啟用 API。

服務帳戶沒有必要權限

如果 Firebase Admin SDK 執行的服務帳戶沒有 iam.serviceAccounts.signBlob 權限,系統可能會顯示以下錯誤訊息: 包括:

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

解決這個問題最簡單的方法,就是將「服務帳戶憑證建立者」IAM 角色授予問題服務帳戶 (通常是 {project-name}@appspot.gserviceaccount.com):

  1. 開啟 IAM 與管理 頁面Google Cloud
  2. 選取您的專案並點選 [繼續]。
  3. 按一下要更新的服務帳戶對應的編輯圖示。
  4. 按一下 [新增其他角色]。
  5. 輸入「Service Account Token Creator」進入搜尋篩選器,然後選取 從結果中找出這個新點
  6. 按一下 [儲存]以確認授予角色

如要進一步瞭解這項程序,請參閱 身分與存取權管理說明文件,或瞭解如何使用 gcloud 指令列工具更新角色。

無法判斷服務帳戶

如果收到類似下列內容的錯誤訊息,表示 Firebase Admin SDK 未正確初始化。

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

如果您要使用 SDK 自動探索服務帳戶 ID,請務必在具備中繼資料伺服器的受管理 Google 環境中部署程式碼。否則,請務必在 SDK 初始化時指定服務帳戶 JSON 檔案或服務帳戶 ID。