カスタム トークンを作成する

Firebase では、保護された JSON Web Token(JWT)を使用したユーザーまたはデバイスの認証が可能であるため、認証に対する完全な制御が得られます。サーバーでこうしたトークンを生成し、クライアント デバイスに返した後、signInWithCustomToken() メソッドで認証するためにこのトークンを使用します。

これを行うには、ユーザー名やパスワードなどのログイン認証情報を受け入れて、その認証情報が有効であればカスタム JWT を返すサーバー エンドポイントを作成する必要があります。クライアント デバイスでは、サーバーから返されたこのカスタム JWT を Firebase(iOS+Androidウェブ)での認証に使用できます。認証後、Firebase Realtime Database や Cloud Storage など他の Firebase サービスにアクセスするときにこの ID が使用されます。また、JWT の内容は、Realtime Database セキュリティ ルールauth オブジェクトと、Cloud Storage セキュリティ ルールrequest.auth オブジェクトでも使用できます。

Firebase Admin SDK でカスタム トークンを作成できます。また、Firebase でネイティブにサポートされない言語でサーバーが記述されている場合は、サードパーティの JWT ライブラリを使用できます。

始める前に

カスタム トークンは署名付き JWT であり、署名に使用される秘密鍵は Google サービス アカウントに属しています。Firebase Admin SDK がカスタム トークンの署名に使用する Google サービス アカウントは、いくつかの方法で指定できます。

  • サービス アカウント JSON ファイルを使用する - この方法はどの環境でも使用できますが、サービス アカウント JSON ファイルをコードとともにパッケージ化する必要があります。サービス アカウント JSON ファイルが外部の人間に公開されないように特別な注意が必要です。
  • Admin SDK にサービス アカウントを検出させる - この方法は、Google Cloud Functions や App Engine など、Google が管理する環境で使用できます。Google Cloud コンソールで追加の権限を構成しなければならない場合があります。
  • サービス アカウント ID を使用する - Google が管理する環境でこの方法を使用すると、指定したサービス アカウントのキーを使用してトークンに署名されます。ただし、リモート ウェブサービスを使用するため、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 の文字列を自動検出できます。検出されたサービス アカウント ID を IAM サービスと一緒に使用することによって、リモートでトークンに署名します。

これらの署名方法を使用するには、SDK を 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 を使用する

アプリケーションのさまざまな部分間で整合性を維持するには、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);

ウェブ向けの名前空間 API

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

ウェブ向けのモジュラー API

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() など)と同様、ユーザーの uid が指定されて Realtime Database セキュリティ ルールauth オブジェクトと Cloud Storage セキュリティ ルールrequest.auth オブジェクトが生成されます。この場合、uid は、カスタム トークンを生成するときに指定したものになります。

Database ルール

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

Storage ルール

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)オブジェクトから参照できます。

Database ルール

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

Storage ルール

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 から最大 3,600 秒後の時間を設定できます。
注: これは、カスタム トークン自体の有効期限が切れる時間のみを制御できます。ただし、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 を使用してトークンに署名します。このエラーは、IAM API が Firebase プロジェクトで現在有効になっていないことを示します。ウェブブラウザでエラー メッセージのリンクを開き、[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)に「サービス アカウント トークン作成者」の IAM ロールを付与することです。

  1. Google Cloud コンソールで [IAM と管理] ページを開きます。
  2. プロジェクトを選択し、[続行] をクリックします。
  3. 更新するサービス アカウントの編集アイコンをクリックします。
  4. [別のロールを追加] をクリックします。
  5. 検索フィルタに「サービス アカウント トークン作成者」と入力し、結果からそれを選択します。
  6. [保存] をクリックしてロールの付与を確定します。

このプロセスの詳細については、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 を指定してください。