Tạo mã thông báo tuỳ chỉnh

Firebase cho bạn toàn quyền kiểm soát đối với quy trình xác thực bằng cách cho phép bạn xác thực người dùng hoặc thiết bị bằng Mã thông báo web JSON (JWT) bảo mật. Bạn tạo các mã thông báo này trên máy chủ của mình, truyền lại các mã thông báo này đến một thiết bị khách, sau đó sử dụng chúng để xác thực thông qua phương thức signInWithCustomToken().

Để đạt được điều này, bạn phải tạo một điểm cuối máy chủ chấp nhận thông tin đăng nhập (chẳng hạn như tên người dùng và mật khẩu). Đồng thời, nếu thông tin đăng nhập hợp lệ, sẽ trả về một JWT tuỳ chỉnh. Sau đó, JWT tuỳ chỉnh được trả về từ máy chủ của bạn có thể được thiết bị khách sử dụng để xác thực với Firebase (iOS+, Android, web). Sau khi được xác thực, danh tính này sẽ được dùng khi truy cập vào các dịch vụ khác của Firebase, chẳng hạn như Cơ sở dữ liệu theo thời gian thực của Firebase và Cloud Storage. Ngoài ra, nội dung của JWT sẽ có trong đối tượng auth trong Quy tắc bảo mật cơ sở dữ liệu theo thời gian thực và đối tượng request.auth trong Quy tắc bảo mật của Cloud Storage.

Bạn có thể tạo mã thông báo tuỳ chỉnh bằng SDK quản trị của Firebase hoặc bạn có thể sử dụng thư viện JWT của bên thứ ba nếu máy chủ của bạn được viết bằng ngôn ngữ mà Firebase không hỗ trợ vốn có.

Trước khi bắt đầu

Mã thông báo tuỳ chỉnh được ký JWT, trong đó khoá riêng tư dùng để ký thuộc về một tài khoản dịch vụ của Google. Có một số cách để chỉ định tài khoản dịch vụ của Google mà SDK quản trị Firebase sẽ sử dụng để ký mã thông báo tuỳ chỉnh:

  • Sử dụng tệp JSON của tài khoản dịch vụ – Bạn có thể sử dụng phương thức này trong bất kỳ môi trường nào, nhưng yêu cầu bạn phải đóng gói tệp JSON của tài khoản dịch vụ cùng với mã của bạn. Bạn phải đặc biệt chú ý để đảm bảo tệp JSON của tài khoản dịch vụ không bị tiết lộ với bên ngoài.
  • Cho phép SDK dành cho quản trị viên khám phá một tài khoản dịch vụ -- Bạn có thể sử dụng phương thức này trong các môi trường do Google quản lý, chẳng hạn như Hàm Google Cloud và App Engine. Bạn có thể phải định cấu hình một số quyền bổ sung thông qua bảng điều khiển Google Cloud.
  • Sử dụng mã tài khoản dịch vụ – Khi được sử dụng trong môi trường do Google quản lý, phương thức này sẽ ký mã thông báo bằng khoá của tài khoản dịch vụ đã chỉ định. Tuy nhiên, dịch vụ này sử dụng một dịch vụ web từ xa và bạn có thể phải định cấu hình các quyền bổ sung cho tài khoản dịch vụ này thông qua bảng điều khiển Google Cloud.

Sử dụng tệp JSON của tài khoản dịch vụ

Các tệp JSON của tài khoản dịch vụ chứa tất cả thông tin tương ứng với các tài khoản dịch vụ (bao gồm cả khoá riêng tư RSA). Bạn có thể tải các tệp này xuống từ bảng điều khiển của Firebase. Làm theo hướng dẫn thiết lập SDK dành cho quản trị viên để biết thêm thông tin về cách khởi động SDK dành cho quản trị viên bằng tệp JSON của tài khoản dịch vụ.

Phương thức khởi chạy này phù hợp với nhiều lần triển khai SDK quản trị. Ngoài ra, API này cho phép SDK dành cho quản trị viên tạo và ký mã thông báo tuỳ chỉnh cục bộ mà không cần thực hiện bất kỳ lệnh gọi API từ xa nào. Nhược điểm chính của phương pháp này là bạn phải đóng gói tệp JSON của tài khoản dịch vụ cùng với mã. Ngoài ra, xin lưu ý rằng khoá riêng tư trong tệp JSON của tài khoản dịch vụ là thông tin nhạy cảm và bạn phải đặc biệt chú ý để giữ bí mật cho khoá đó. Cụ thể, không nên thêm tệp JSON của tài khoản dịch vụ vào quy trình quản lý phiên bản công khai.

Cho phép SDK dành cho quản trị viên khám phá một tài khoản dịch vụ

Nếu mã của bạn được triển khai trong một môi trường do Google quản lý, thì SDK dành cho quản trị viên có thể tìm cách tự động khám phá một phương tiện để ký mã thông báo tuỳ chỉnh:

  • Nếu mã của bạn được triển khai trong môi trường tiêu chuẩn của App Engine cho Java, Python hoặc Go, thì SDK dành cho quản trị viên có thể dùng dịch vụ Danh tính ứng dụng có trong môi trường đó để ký mã thông báo tuỳ chỉnh. Dịch vụ Danh tính ứng dụng sẽ ký dữ liệu bằng tài khoản dịch vụ do Google App Engine cung cấp cho ứng dụng của bạn.

  • Nếu mã của bạn được triển khai trong một số môi trường được quản lý khác (ví dụ: Hàm Google Cloud, Google Compute Engine), thì SDK quản trị Firebase có thể tự động phát hiện một chuỗi mã tài khoản dịch vụ từ máy chủ siêu dữ liệu cục bộ. Sau đó, mã tài khoản dịch vụ được phát hiện sẽ được dùng cùng với dịch vụ IAM để ký mã thông báo từ xa.

Để sử dụng các phương thức ký này, hãy khởi chạy SDK bằng thông tin xác thực Mặc định của ứng dụng Google và không chỉ định chuỗi mã tài khoản dịch vụ:

Node.js

initializeApp();

Java

FirebaseApp.initializeApp();

Python

default_app = firebase_admin.initialize_app()

Tiến hành

app, err := firebase.NewApp(context.Background(), nil)
if err != nil {
	log.Fatalf("error initializing app: %v\n", err)
}

C#

FirebaseApp.Create();

Để kiểm thử cục bộ cùng một mã, hãy tải tệp JSON của tài khoản dịch vụ xuống rồi đặt biến môi trường GOOGLE_APPLICATION_CREDENTIALS để trỏ đến tệp đó.

Nếu SDK quản trị Firebase phải khám phá một chuỗi mã tài khoản dịch vụ, thì SDK đó sẽ thực hiện việc này khi mã của bạn tạo mã thông báo tuỳ chỉnh lần đầu tiên. Kết quả này sẽ được lưu vào bộ nhớ đệm và sử dụng lại cho các hoạt động ký mã thông báo tiếp theo. Mã tài khoản dịch vụ được tự động phát hiện thường là một trong những tài khoản dịch vụ mặc định do Google Cloud cung cấp:

Tương tự như mã tài khoản dịch vụ được chỉ định rõ ràng, mã tài khoản dịch vụ được tự động phát hiện phải có quyền iam.serviceAccounts.signBlob thì quá trình tạo mã thông báo tuỳ chỉnh mới có thể hoạt động. Có thể bạn phải sử dụng phần IAM và quản trị viên trên bảng điều khiển Google Cloud để cấp các quyền cần thiết cho các tài khoản dịch vụ mặc định. Hãy xem phần khắc phục sự cố bên dưới để biết thêm chi tiết.

Sử dụng mã tài khoản dịch vụ

Để duy trì tính nhất quán giữa các phần của ứng dụng, bạn có thể chỉ định mã tài khoản dịch vụ có các khoá sẽ được dùng để ký mã thông báo khi chạy trong môi trường do Google quản lý. Việc này có thể giúp các chính sách IAM trở nên đơn giản và an toàn hơn, đồng thời bạn không cần phải đưa tệp JSON của tài khoản dịch vụ vào mã của mình.

Bạn có thể tìm thấy mã tài khoản dịch vụ trong bảng điều khiển Google Cloud hoặc trong trường client_email của tệp JSON tài khoản dịch vụ đã tải xuống. Mã tài khoản dịch vụ là các địa chỉ email có định dạng sau: <client-id>@<project-id>.iam.gserviceaccount.com. Các dịch vụ này xác định riêng biệt các tài khoản dịch vụ trong các dự án Firebase và Google Cloud.

Để tạo mã thông báo tuỳ chỉnh bằng mã tài khoản dịch vụ riêng, hãy khởi chạy SDK như sau:

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)

Tiến hành

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

Mã tài khoản dịch vụ không phải là thông tin nhạy cảm nên việc hiển thị thông tin là không quan trọng. Tuy nhiên, để ký mã thông báo tuỳ chỉnh bằng tài khoản dịch vụ đã chỉ định, SDK dành cho quản trị viên Firebase phải gọi một dịch vụ từ xa. Ngoài ra, bạn cũng phải đảm bảo rằng tài khoản dịch vụ mà SDK dành cho quản trị viên đang sử dụng để thực hiện lệnh gọi này (thường là {project-name}@appspot.gserviceaccount.com) có quyền iam.serviceAccounts.signBlob. Hãy xem phần khắc phục sự cố bên dưới để biết thêm chi tiết.

Tạo mã thông báo tuỳ chỉnh bằng SDK quản trị của Firebase

SDK quản trị của Firebase có phương thức tích hợp để tạo mã thông báo tuỳ chỉnh. Ở mức tối thiểu, bạn cần cung cấp một uid. Giá trị này có thể là chuỗi bất kỳ nhưng phải xác định duy nhất người dùng hoặc thiết bị mà bạn đang xác thực. Các mã thông báo này sẽ hết hạn sau một giờ.

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)

Tiến hành

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

Bạn cũng có thể tuỳ ý chỉ định các thông báo xác nhận quyền sở hữu khác để đưa vào mã thông báo tuỳ chỉnh. Ví dụ: bên dưới, một trường premiumAccount đã được thêm vào mã thông báo tuỳ chỉnh. Mã này sẽ có trong các đối tượng auth / request.auth trong Quy tắc bảo mật của bạn:

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)

Tiến hành

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

Tên dành riêng cho mã thông báo tuỳ chỉnh

Đăng nhập bằng mã thông báo tuỳ chỉnh trên ứng dụng

Sau khi tạo mã thông báo tuỳ chỉnh, bạn nên gửi mã đó đến ứng dụng khách của mình. Ứng dụng này xác thực bằng mã thông báo tuỳ chỉnh bằng cách gọi signInWithCustomToken():

iOS trở lên

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

API không gian tên trên web

firebase.auth().signInWithCustomToken(token)
  .then((userCredential) => {
    // Signed in
    var user = userCredential.user;
    // ...
  })
  .catch((error) => {
    var errorCode = error.code;
    var errorMessage = error.message;
    // ...
  });

API mô-đun 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;
    // ...
  });

Nếu xác thực thành công, thì người dùng sẽ được đăng nhập vào ứng dụng bằng tài khoản do uid chỉ định trong mã thông báo tuỳ chỉnh. Nếu tài khoản đó trước đó không tồn tại, thì một bản ghi cho người dùng đó sẽ được tạo.

Tương tự như các phương thức đăng nhập khác (chẳng hạn như signInWithEmailAndPassword()signInWithCredential()), đối tượng auth trong Quy tắc bảo mật cơ sở dữ liệu theo thời gian thực và đối tượng request.auth trong Quy tắc bảo mật của Cloud Storage sẽ được điền sẵn uid của người dùng. Trong trường hợp này, uid sẽ là mã mà bạn đã chỉ định khi tạo mã thông báo tuỳ chỉnh.

Quy tắc về cơ sở dữ liệu

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

Quy tắc lưu trữ

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

Nếu mã thông báo tuỳ chỉnh có chứa các thông báo xác nhận quyền sở hữu khác, thì các thông báo này có thể được tham chiếu từ đối tượng auth.token (Cơ sở dữ liệu theo thời gian thực của Firebase) hoặc request.auth.token (Bộ nhớ trên đám mây) trong các quy tắc của bạn:

Quy tắc về cơ sở dữ liệu

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

Quy tắc lưu trữ

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

Tạo mã thông báo tuỳ chỉnh bằng thư viện JWT của bên thứ ba

Nếu phần phụ trợ của bạn bằng ngôn ngữ không có SDK quản trị Firebase chính thức, thì bạn vẫn có thể tạo mã thông báo tuỳ chỉnh theo cách thủ công. Trước tiên, hãy tìm thư viện JWT của bên thứ ba cho ngôn ngữ của bạn. Sau đó, hãy sử dụng thư viện JWT đó để tạo JWT bao gồm các thông báo xác nhận quyền sở hữu sau:

Thông báo xác nhận quyền sở hữu mã thông báo tuỳ chỉnh
alg Thuật toán "RS256"
iss Tổ chức phát hành Địa chỉ email tài khoản dịch vụ của dự án của bạn
sub Tiêu đề Địa chỉ email tài khoản dịch vụ của dự án của bạn
aud Đối tượng "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat Được phát hành tại thời điểm Thời gian hiện tại, tính bằng giây kể từ thời gian bắt đầu của hệ thống UNIX
exp Thời gian hết hạn Khoảng thời gian (tính bằng giây) kể từ thời gian bắt đầu của hệ thống UNIX mà mã thông báo hết hạn. Thời điểm này có thể sớm hơn tối đa 3.600 giây so với iat.
Lưu ý: hành động này chỉ kiểm soát thời gian mà mã thông báo tuỳ chỉnh hết hạn. Tuy nhiên, sau khi bạn đăng nhập người dùng bằng signInWithCustomToken(), họ sẽ vẫn duy trì trạng thái đăng nhập vào thiết bị cho đến khi phiên của họ hết hiệu lực hoặc người dùng đăng xuất.
uid Giá trị nhận dạng duy nhất của người dùng đã đăng nhập phải là một chuỗi, dài từ 1 đến 128 ký tự. uid ngắn hơn có hiệu suất tốt hơn.
claims (không bắt buộc) Thông báo xác nhận quyền sở hữu tuỳ chỉnh không bắt buộc để đưa vào các biến auth/request.auth của Quy tắc bảo mật

Dưới đây là một số ví dụ về cách triển khai mã thông báo tuỳ chỉnh bằng nhiều ngôn ngữ mà SDK quản trị Firebase không hỗ trợ:

PHP

Sử dụng 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

Sử dụng 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

Sau khi bạn tạo mã thông báo tuỳ chỉnh, hãy gửi mã đó đến ứng dụng khách để xác thực với Firebase. Hãy xem các mã mẫu ở trên để biết cách thực hiện việc này.

Khắc phục sự cố

Phần này trình bày một số vấn đề phổ biến mà nhà phát triển có thể gặp phải khi tạo mã thông báo tuỳ chỉnh và cách giải quyết các vấn đề đó.

Chưa bật IAM API

Nếu đang chỉ định mã tài khoản dịch vụ cho mã thông báo ký, bạn có thể gặp lỗi tương tự như sau:

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.

SDK quản trị của Firebase sử dụng API IAM để ký mã thông báo. Lỗi này cho biết rằng API IAM hiện chưa được bật cho dự án Firebase của bạn. Mở đường liên kết trong thông báo lỗi trên một trình duyệt web và nhấp vào nút "Enable API" (Bật API) để bật API đó cho dự án của bạn.

Tài khoản dịch vụ không có các quyền cần thiết

Nếu tài khoản dịch vụ mà SDK quản trị của Firebase đang chạy do không có quyền iam.serviceAccounts.signBlob, thì bạn có thể nhận được thông báo lỗi như sau:

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

Cách dễ nhất để giải quyết vấn đề này là cấp vai trò quản lý danh tính và quyền truy cập (IAM) "Người tạo mã thông báo tài khoản dịch vụ" cho tài khoản dịch vụ có liên quan, thường là {project-name}@appspot.gserviceaccount.com:

  1. Mở trang IAM và quản trị viên trong bảng điều khiển Google Cloud.
  2. Chọn dự án của bạn rồi nhấp vào "Tiếp tục".
  3. Nhấp vào biểu tượng chỉnh sửa tương ứng với tài khoản dịch vụ mà bạn muốn cập nhật.
  4. Nhấp vào "Thêm vai trò khác".
  5. Nhập "Service Account Token Creator" (Trình tạo mã thông báo tài khoản dịch vụ) vào bộ lọc tìm kiếm rồi chọn trong kết quả.
  6. Nhấp vào "Lưu" để xác nhận việc cấp vai trò.

Hãy tham khảo tài liệu về IAM để biết thêm thông tin về quy trình này, hoặc tìm hiểu cách cập nhật vai trò bằng các công cụ dòng lệnh gcloud.

Không xác định được tài khoản dịch vụ

Nếu bạn nhận được thông báo lỗi tương tự như sau, thì tức là SDK quản trị của Firebase chưa được khởi chạy đúng cách.

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

Nếu bạn đang dựa vào SDK để tự động khám phá mã tài khoản dịch vụ, hãy đảm bảo mã được triển khai trong môi trường được quản lý của Google có máy chủ siêu dữ liệu. Nếu không, hãy nhớ chỉ định tệp JSON của tài khoản dịch vụ hoặc mã tài khoản dịch vụ khi khởi chạy SDK.