Firebase 可讓您使用安全的 JSON Web Token (JWT) 驗證使用者或裝置,全面掌控驗證作業。您可以在伺服器上產生這些權杖,然後傳回給用戶端裝置,再透過 signInWithCustomToken()
方法使用這些權杖進行驗證。
如要達成這個目標,您必須建立伺服器端點,接受登入憑證 (例如使用者名稱和密碼),並在憑證有效時傳回自訂 JWT。接著,用戶端裝置就能使用伺服器傳回的自訂 JWT 向 Firebase 進行驗證 (iOS+、Android、網頁)。驗證完成後,存取其他 Firebase 服務 (例如 Firebase Realtime Database 和 Cloud Storage) 時,系統會使用這個身分。此外,JWT 的內容會顯示在 Realtime Database Security Rules 中的 auth
物件,以及 Cloud Storage Security Rules 中的 request.auth
物件。
您可以使用 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 部署作業。此外,這項功能可讓 Admin SDK 在本機建立及簽署自訂權杖,不必發出任何遠端 API 呼叫。這種做法的主要缺點是,您必須將服務帳戶 JSON 檔案與程式碼一併封裝。此外,服務帳戶 JSON 檔案中的私密金鑰屬於機密資訊,請務必妥善保管。具體來說,請勿將服務帳戶 JSON 檔案新增至公開版本控管。
讓 Admin SDK 探索服務帳戶
如果您的程式碼部署在 Google 管理的環境中,Admin SDK 可以嘗試自動探索簽署自訂權杖的方法:
如果您的程式碼部署在 Java、Python 或 Go 的App Engine標準環境中,Admin SDK 可以使用該環境中的 App Identity 服務簽署自訂權杖。App Identity 服務會使用 Google App Engine 為應用程式佈建的服務帳戶簽署資料。
如果您的程式碼部署在其他受管理環境 (例如 Google Cloud Functions、Google Compute Engine),Firebase Admin SDK 可以從本機中繼資料伺服器自動探索服務帳戶 ID 字串。然後,系統會搭配 IAM 服務使用探索到的服務帳戶 ID,遠端簽署權杖。
如要使用這些簽署方法,請使用 Google 應用程式預設憑證初始化 SDK,且不要指定服務帳戶 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 and admin」(IAM 與管理員) 部分,授予預設服務帳戶必要權限。詳情請參閱下方的疑難排解部分。
使用服務帳戶 ID
為確保應用程式各部分保持一致,您可以在 Google 管理的環境中執行時,指定要使用金鑰簽署權杖的服務帳戶 ID。這樣一來,IAM 政策會更簡單安全,您也不必在程式碼中加入服務帳戶 JSON 檔案。
服務帳戶 ID 位於 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
,可以是任何字串,但應能唯一識別您要驗證的使用者或裝置。這些權杖會在 1 小時後失效。
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 Epoch 紀元時間為始 (單位為秒) |
exp |
到期時間 |
權杖到期時間,以秒為單位,自 UNIX Epoch 紀元時間開始算起。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。在網頁瀏覽器中開啟錯誤訊息中的連結,然後按一下「啟用 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}.
如要解決這個問題,最簡單的方法是將「服務帳戶憑證建立者」身分與存取權管理角色授予有問題的服務帳戶,通常是 {project-name}@appspot.gserviceaccount.com
:
- 在 Google Cloud 控制台中開啟「IAM and admin」(IAM 與管理) 頁面。
- 選取專案並按一下「繼續」。
- 找到要更新的服務帳戶,然後點選對應的編輯圖示。
- 按一下「新增其他角色」。
- 在搜尋篩選器中輸入「服務帳戶憑證建立者」,然後從結果中選取。
- 按一下「儲存」確認授予角色。
如要進一步瞭解這個程序,請參閱 IAM 說明文件,或瞭解如何使用 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。