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

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

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

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

準備

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

  • サービス アカウント JSON ファイルを使用する - この方法はどの環境でも使用できますが、サービス アカウント JSON ファイルをコードとともにパッケージ化する必要があります。サービス アカウント JSON ファイルが外部の人間に公開されないように特別な注意が必要です。
  • サービス アカウント ID を使用する - この方法はどの環境でも使用でき、多くの場合、完全なサービス アカウント JSON ファイルを使用するよりも簡単で安全です。ただし、リモート ウェブサービスを使用してトークンに署名するため、Google Cloud Platform Console で追加の権限を構成しなければならない場合があります。
  • Admin SDK にサービス アカウントを検出させる - この方法は、Google Cloud Functions や Google App Engine など、Google が管理する環境で使用できます。Google Cloud Platform Console で追加の権限を構成しなければならない場合があります。

サービス アカウント JSON ファイルを使用する

サービス アカウント JSON ファイルには、サービス アカウントに関するすべての情報が含まれます(RSA 秘密鍵も含まれます)。このファイルは Firebase コンソールからダウンロードできます。サービス アカウント JSON ファイルを使用して Admin SDK を初期化する方法の詳細については、Admin SDK のセットアップ手順をご覧ください。

この初期化方法は、さまざまな Admin SDK のデプロイに適しています。また、Admin SDK が、リモート API 呼び出しを行わずにローカルでカスタム トークンを作成して署名できるようになります。このアプローチの主な欠点は、サービス アカウント JSON ファイルをコードとともにパッケージ化する必要があることです。また、サービス アカウント JSON ファイル内の秘密鍵は機密情報であり、機密性を保持するために特別な注意が必要です。サービス アカウント JSON ファイルを一般公開のバージョン コントロールに追加しないよう、特に注意してください。

サービス アカウント ID を使用する

完全なサービス アカウント JSON ファイルをコードとともにパッケージ化する代わりに、サービス アカウント ID の文字列だけを使用して Firebase Admin SDK を初期化できます。この文字列は Firebase コンソールで確認できます。また、ダウンロードしたサービス アカウント JSON ファイルの client_email フィールドにも含まれています。サービス アカウント ID は、<client-id>@<project-id>.iam.gserviceaccount.com という形式のメールアドレスです。この ID によって、Firebase や Google Cloud Platform のプロジェクトのサービス アカウントが一意に識別されます。

サービス アカウント ID のみを使用してカスタム トークンを作成するには、次のように SDK を初期化します。

Node.js

admin.initializeApp({
  serviceAccountId: 'my-client-id@my-project-id.iam.gserviceaccount.com',
});

Java

FirebaseOptions options = new FirebaseOptions.Builder()
    .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 は機密情報ではないため、これが他人に知られても特に問題はありません。ただし、サービス アカウント ID だけでカスタム トークンに署名するには、Firebase Admin SDK がリモート サービスを呼び出す必要があります。さらに、この目的で選択したサービス アカウントには iam.serviceAccounts.signBlob 権限も必要です。詳細については、以下のトラブルシューティングのセクションをご覧ください。

Admin SDK にサービス アカウントを検出させる

コードが Google によって管理されている環境にデプロイされている場合は、Admin SDK に対して、カスタム トークンに署名する手段の自動検出を試みさせることができます。

  • Java、Python、Go 向けの Google 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

admin.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 Platform によって提供されるデフォルトのサービス アカウントの 1 つです。

自動的に検出されるサービス アカウント ID には、明示的に指定されたサービス アカウント ID と同様に、カスタム トークンを作成できるようにするための iam.serviceAccounts.signBlob 権限が必要です。デフォルトのサービス アカウントに必要な権限を与えるために、Google Cloud Platform Console の [IAM と管理] セクションでの操作が必要になることがあります。詳細については、以下のトラブルシューティングのセクションをご覧ください。

Firebase Admin SDK を使用したカスタム トークンの作成

Firebase Admin SDK には、カスタム トークンを作成するための組み込みメソッドが用意されています。最低限 uid を指定する必要があり、任意の文字列を指定できますが、認証中のユーザーまたは端末を一意に識別するものである必要があります。これらのトークンは 1 時間後に失効します。

Node.js

var uid = "some-uid";

admin.auth().createCustomToken(uid)
  .then(function(customToken) {
    // Send token back to client
  })
  .catch(function(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

オプションで、カスタム トークンに追加のクレームを含めるよう指定することもできます。たとえば、セキュリティ ルールの auth / request.auth オブジェクトで使用可能なカスタム トークンに次の premiumAccount フィールドが追加されています。

Node.js

var uid = "some-uid";
var additionalClaims = {
  premiumAccount: true
};

admin.auth().createCustomToken(uid, additionalClaims)
  .then(function(customToken) {
    // Send token back to client
  })
  .catch(function(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.FirebaseUser newUser = task.Result;
  Debug.LogFormat("User signed in successfully: {0} ({1})",
      newUser.DisplayName, newUser.UserId);
});

C++

firebase::Future<firebase::auth::User*> result =
    auth->SignInWithCustomToken(custom_token);

ウェブ

firebase.auth().signInWithCustomToken(token).catch(function(error) {
  // Handle Errors here.
  var errorCode = error.code;
  var errorMessage = error.message;
  // ...
});

認証が成功した場合、ユーザーはカスタム トークンに含まれる uid によって指定されているアカウントでクライアント アプリにログインします。そのアカウントがまだ存在しなければ、該当ユーザーのレコードが作成されます。

その他のログイン方法(signInWithEmailAndPassword()signInWithCredential() など)と同様に、Firebase Realtime Database セキュリティ ルールauth オブジェクトや Cloud Storage セキュリティ ルールrequest.auth オブジェクトにもユーザーの uid が指定されます。この場合、カスタム トークンの生成時に指定した 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.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~36 文字の文字列にする必要があります)。
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 のみを使用してカスタム トークンを作成している場合(ID をコードに明示的に指定した場合、または 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 を有効にする] ボタンをクリックしてプロジェクトで有効にします。

サービス アカウントに必要な権限がない

トークンに署名するために使用されるサービス アカウント ID に iam.serviceAccounts.signBlob 権限がない場合、次のようなエラー メッセージが表示されることがあります。

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

これを解決する最も簡単な方法は、問題のサービス アカウントに「サービス アカウント トークン作成者」の IAM 役割を付与することです。

  1. Google Cloud Platform Console で [IAM と管理] ページを開きます。
  2. プロジェクトを選択し、[続行] をクリックします。
  3. 更新するサービス アカウント ID の編集アイコンをクリックします。
  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 を指定してください。

フィードバックを送信...

ご不明な点がありましたら、Google のサポートページをご覧ください。