🛡️

Firebase Authentication with Identity Platform ブロッキング関数による外部 IdP 連携の強化

2025/04/03に公開

はじめに

Google Cloud Partner Top Engineer (Serverless App Development) 2025 の 小堀内 です。

現代のアプリケーション開発において、外部の ID プロバイダ (IdP) との連携は、セキュリティ強化や UX 向上に不可欠です。

複数のソーシャルログイン (Google, GitHub, Twitter など) やエンタープライズ向けの認証システム (Auth0, Okta, Entra ID など) との連携は、ユーザーにとって利便性が高く、開発者にとっては認証機能の実装と管理の負担を軽減します。

しかし、連携する IdP が増えるほど、認証フローのカスタマイズやセキュリティ要件への対応が複雑になるという課題も存在します。

本記事では Google Cloud の Identity PlatformFirebase Authentication with Identity Platform が提供する機能である ブロッキング関数 (Blocking Functions) に焦点を当て、特に外部 IdP との連携 において、この機能がどのように役立つのかを解説します。

本記事での用語について

ここで、本記事で扱う認証サービスについて整理しておきます。
これらのサービスは名称が似ており、混乱を招きやすいためです。

サービス名称 説明
Firebase Authentication Google が提供する認証サービス
Firebase Authentication with Identity Platform Firebase Authenticationに 高度な機能を追加した上位サービス
(本記事はこちらを主軸に解説)
Identity Platform Firebase Authentication (with Identity Platform 含む) のバックエンドであり、Google Cloud 上で同等の機能を提供するサービス

これら 3 つは同じバックエンド (Identity Platform) を共有しており、主な違いは利用できる機能セットと管理コンソールです。

提供される機能の範囲は、以下の関係性になります。

Firebase Authentication < Firebase Authentication with Identity Platform = Identity Platform

この関係性を図で示すと以下のようになります。

本記事では主に Firebase Authentication with Identity Platform の視点から説明を進めますが、基盤となる技術は Identity Platform と共通 です。
特に区別が必要な場合には、その都度言及します。

本記事のスコープ

説明すること

  • ブロッキング関数の概要と基本機能
  • 外部 IdP 連携におけるブロッキング関数の活用シナリオ
  • 実装方法とコード例
  • テスト戦略と課題
  • デプロイ手順と注意点

説明しないこと

  • 認証の基本概念や一般的な実装方法
  • 特定の IdP に依存した詳細な連携手順
  • 外部 IdP 自体の設定方法や詳細
  • マルチテナント環境の構築方法

ブロッキング関数 (Blocking Functions) とは

ブロッキング関数とは、アカウント作成前 (beforeCreate) または サインイン前 (beforeSignIn) の認証処理の途中でトリガーされる Cloud Run functions です。

この関数内で独自のカスタムコードを実行することにより、認証の許可 または 拒否、ユーザー情報の変更や、カスタムクレームの付与など、認証フローを柔軟にカスタマイズすることができます。

(参考: もし Auth0 を利用された経験があれば、同様のコンセプトを持つ機能である Auth0 Actions を想像していただくと、ブロッキング関数が認証フローの特定ポイントでカスタムロジックを実行する役割をイメージしやすいかもしれません。)

トリガー 発動タイミング 主な用途/ユースケース
アカウント作成前
(beforeCreate)
新しいユーザーが Identity Platform の DB に保存される前、およびトークンがクライアントアプリに返される前 • ユーザー登録条件の厳格化
• 登録時の初期設定
• 特定ドメインのメールアドレスのみ許可
• 未確認メールアドレスの登録ブロック
サインイン前
(beforeSignIn)
ユーザーの認証情報が検証されてから、Identity Platform からクライアントアプリに ID トークンが返される前
(MFA 使用時は第 2 要素検証後)
• ログイン時のアクセス制限
• 不審なアクティビティの検出
• 最終ログイン時間の記録

これら2種類のトリガーがどのようなタイミングで発火し、認証フローの中でどのような役割を果たすのかを以下のシーケンス図で示します。

上記は、ブロッキング関数における 2 つのトリガーの違いと用途を簡潔に表や図にまとめたものです。

この機能に関する公式ドキュメントは以下をご参照ください。

外部 IdP 連携におけるブロッキング関数の活用

Identity Platform と Firebase Authentication with Identity Platform は様々な IdP との連携をサポートしています。

具体的には Google, Facebook, Twitter, GitHub, Microsoft, Apple などのソーシャルログインに加え、SAML や OIDC といったエンタープライズ向けの認証プロトコルを利用した連携が可能です。

エンタープライズ環境では Entra ID (旧 Azure AD), Okta, Auth0 など多様な外部 IdP が利用されています。
ブロッキング関数を活用することで、外部 IdP から提供される情報に基づいて、より細やかなアクセス制御やユーザー管理を実現できます。

example
アーキテクチャ例

以下に、外部 IdP 連携におけるブロッキング関数の具体的な活用例を挙げます。

ユースケース 説明
特定のドメインのユーザーのみ許可 特定のメールアドレスドメイン (例: @example.com) を持つユーザーのみアプリケーションへの登録やログインを許可することができる
IP アドレスによるアクセス制限と不審なアクティビティの監視 ユーザーの登録やログイン元の IP アドレスに基づいてアクセスを制限したり、不審な IP アドレスからのアクセスをブロックすることができる
カスタムクレームの付与によるアクセス制御 外部 IdP から取得したユーザー情報に基づいてカスタムクレームを付与することで、アプリケーション内で RBAC (ロールベースアクセス制御) などを実現することができる
未承認ユーザーのブロック 外部 IdP での認証が成功した後に、事前に承認されたユーザーリスト (Firestore やスプレッドシートなどに保存) と照合し、リストにないユーザーの登録やログインを Google Cloud の世界に入ってくる前にブロックすることができる

ブロッキング関数 (Blocking Functions) の実装方法

1. Firebase Authentication -> Firebase Authentication with Identity Platform へのアップグレード

Firebase Authentication コンソールからアップグレードを実施します。

1-upgrade
Firebase コンソール > Authentication コンソール > 設定メニュー > ブロッキング関数

2-upgraded
Firebase Authentication with Identity Platform へのアップグレード後

2. ブロッキング関数の実装

ブロッキング関数のロジックを実装した Cloud Run functions を作成します。

2-1. SDK の選択

ブロッキング関数を実装する方法は 2 種類あり、それぞれ異なる SDK を使用します。

ただし、どちらの方式も裏側では Identity Platform および Cloud Run functions が使用されるため、機能面での違いはありません。

SDK 選択は実装者の言語の好みや既存のプロジェクト環境に応じて選ぶとよいと思います。

実装方法 対応SDK/開発環境 対応デプロイ先
Identity Platform JavaScript/TypeScript (Node.js)
gcip-cloud-functions
• Cloud Run functions (1st Gen)
• Cloud Run functions (2nd Gen)
Firebase Authentication with Identity Platform JavaScript/TypeScript (Node.js)
firebase-functions
Python
firebase_functions.identity_fn
• Cloud Run functions (1st Gen)
• Cloud Run functions (2nd Gen)

2-2. 実装 (コード例)

今回の実装例では、以下のマルチテナント環境向け要件でブロッキング関数を実装します。

  • ① 特定テナントのみドメイン制限を適用
    • tenant-example テナントでは @example.com ドメインのみ許可
    • tenant-corporate テナントでは @corporate.com ドメインのみ許可
    • その他のテナントは制限なし (ブロッキング関数をスキップ)
  • ② アカウント作成時のみドメイン制限を適用
    • アカウント作成前 (beforeCreate) のみ検証を行う
index.ts
import * as gcipCloudFunctions from 'gcip-cloud-functions';

const auth = new gcipCloudFunctions.Auth();

export enum CheckResult {
  SKIP = 0,
  ALLOWED = 1,
  BLOCKED_DOMAIN = 2
}

// ユーザーのテナントとメールドメインを検証するビジネスロジック
export function evaluateUser(tenantId: string, email: string): CheckResult {
  // テナントIDが特定のものでない場合はスキップ
  if (tenantId !== 'tenant-example' && tenantId !== 'tenant-corporate') {
    return CheckResult.SKIP;
  }

  // テナントごとの許可ドメインチェック
  if (tenantId === 'tenant-example') {
    if (email.endsWith('@example.com')) {
      return CheckResult.ALLOWED;
    }
  } else if (tenantId === 'tenant-corporate') {
    if (email.endsWith('@corporate.com')) {
      return CheckResult.ALLOWED;
    }
  }
  
  // 許可ドメインに一致しない場合はブロック
  return CheckResult.BLOCKED_DOMAIN;
}

// アカウント作成前に実行されるブロッキング関数
export const beforeCreate = auth.functions().beforeCreateHandler((user, context) => {
  const result = evaluateUser(user.tenantId || '', user.email || '');

  if (result === CheckResult.SKIP) {
    // このテナントはドメイン制限がないためスキップ
    return {};
  } else if (result === CheckResult.BLOCKED_DOMAIN) {
    // テナントに応じたエラーメッセージを生成
    let errorMessage = '';
    if (user.tenantId === 'tenant-example') {
      errorMessage = '@example.com ドメインのメールアドレスを持つユーザーのみアクセスできます。';
    } else if (user.tenantId === 'tenant-corporate') {
      errorMessage = '@corporate.com ドメインのメールアドレスを持つユーザーのみアクセスできます。';
    } else {
      errorMessage = 'このメールドメインはアクセスを許可されていません。';
    }
    
    throw new gcipCloudFunctions.https.HttpsError(
      'permission-denied',
      errorMessage
    );
  }

  return {};
});

2-3. ブロッキング関数で取得できる値

実装コード例の beforeCreateHandler 内で usercontext という引数を受け取っています。

これらは、ブロッキング関数が beforeCreatebeforeSignIn によってトリガーされた際に、外部 IdP や Identity Platform から渡される情報を含んでいます。

user オブジェクトから取得可能な値

Identity Platform, Firebase Authentication with Identity Platform に登録される想定のユーザーレコードの情報 (UserRecord) を取得することができます。

context オブジェクトから取得可能な値

外部 IdP から渡される情報 (AdditionalUserInfo および AuthCredential) を取得することができます。

AuthCredential の中の claims プロパティは、特に SAML や OIDC による外部 IdP 連携において重要です。
ここには IdP が送信する属性情報 (クレーム) が格納されます。

Entra ID (旧 Azure AD) との連携時のクレーム例

例えば、Entra ID (旧 Azure AD) と連携した場合、claims オブジェクトには以下のようなキーで情報が含まれてきます。

  • SAML 連携時
    • http://schemas.microsoft.com/identity/claims/tenantid (テナント ID)
    • http://schemas.microsoft.com/identity/claims/objectidentifier (ユーザーオブジェクト ID)
    • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress (メールアドレス)
    • など
  • OIDC 連携時
    • oid (ユーザーオブジェクト ID)
    • tid (テナント ID)
    • preferred_username (優先ユーザー名)
    • など

より詳細な情報については、以下の Microsoft のドキュメントをご参照ください。

2-4. テスト戦略とその課題

ブロッキング関数は、アプリケーションの認証フローにおける重要な制御ポイントです。
そのため、実装したロジックが意図通りに動作することを保証するためのテストは不可欠です。
しかし、ブロッキング関数のテストには特有の課題が存在します。

課題: Firebase Local Emulator Suite の制限

通常、Firebase 関連の機能を開発する際には Firebase Local Emulator Suite を活用してローカル環境でテストを行うことが多いでしょう。

もちろん、Firebase Authentication with Identity Platform のエミュレートもローカルにて可能です。

しかし、現時点では、Firebase Local Emulator Suite は ブロッキング関数 (beforeCreate および beforeSignIn トリガー) のエミュレーションをサポートしていません

参考:

これは、beforeCreatebeforeSignIn イベントの発火をローカルで再現し、イベントデータ (usercontext オブジェクト) を受け取って関数全体の動作を確認する、といった 統合テストをローカル環境で行うことが困難である ことを意味します。

対策: ビジネスロジックの分離とユニットテスト

この制約に対応するための現実的なアプローチは、認証に関するコアなビジネスロジックを、イベントハンドラ関数から分離する ことです。

上記実装例における evaluateUser 関数がこれにあたります。

このようにロジックを分離することで、その部分は Identity Platform のイベントハンドラに依存しない、独立した関数となります。

これにより、Jest のような標準的なテストフレームワークを用いたユニットテストが可能になります。

以下は、evaluateUser 関数に対する Jest を用いたユニットテストの例です。

evaluateUser.test.ts
import { CheckResult, evaluateUser } from './index';

describe('ドメイン制限のビジネスロジック単体テスト', () => {
  // テストケース定義
  type TestCase = [string, string, CheckResult, string];
  const testCases: TestCase[] = [
    // テナントID, メールアドレス, 期待結果, 説明
    ['tenant-example', 'user@example.com', CheckResult.ALLOWED, '許可ケース: example テナントで、example.com ドメインなら許可'],
    ['tenant-example', 'user@gmail.com', CheckResult.BLOCKED_DOMAIN, '拒否ケース: example テナントで、ドメインが対象外であれば拒否'],
    ['tenant-corporate', 'user@corporate.com', CheckResult.ALLOWED, '許可ケース: corporate テナントで、corporate.com ドメインなら許可'],
    ['tenant-corporate', 'user@example.com', CheckResult.BLOCKED_DOMAIN, '拒否ケース: corporate テナントで、ドメインが対象外であれば拒否'],
    ['other-tenant', 'user@example.com', CheckResult.SKIP, 'スキップケース: 対象外テナントの場合は、ブロック判定スキップ'],
    ['', 'user@example.com', CheckResult.SKIP, 'スキップケース: テナントIDがない場合は、ブロック判定スキップ'],
  ];

  // 単体テストケースの実行
  test.each(testCases)(
    'テナントID="%s", メールアドレス="%s" の場合、%s を返す(%s)',
    (tenantId, email, expected, description) => {
      const result = evaluateUser(tenantId, email);
      expect(result).toBe(expected);
    }
  );
});

このユニットテストでは、様々なテナント ID とメールアドレスの組み合わせに対して、evaluateUser 関数が期待通りの結果 (ALLOWED, BLOCKED_DOMAIN, SKIP) を返すかを検証しています。

これにより、少なくとも 中核となる判定ロジック部分の正確性 は担保することができます。

unittest
ユニットテスト成功イメージ

ユニットテストの限界と実環境での確認

ただし、ユニットテストだけではカバーできない範囲もあります。

  • イベントハンドラ部分: beforeCreate ハンドラ内で usercontext オブジェクトから正しく値を取得できているか、エラー (HttpsError) が適切にスローされているか、などの部分は検証が難しいです。
  • Identity Platform との連携: 実際に Identity Platform, Firebase Authentication with Identity Platform が関数を呼び出し、返却値やエラーを正しく解釈して認証フローを制御するかどうかは、実環境での確認が必要です。

3. Cloud Run functions へのデプロイ

ブロッキング関数は Cloud Run functions 1st Gen と 2nd Gen のどちらにもデプロイが可能です。

本記事では、基盤として Cloud Run を利用し、より高度なネットワーク機能や長いリクエストタイムアウトなどの利点がある Cloud Run functions (2nd Gen) を使用してデプロイを実施します。

参考: Cloud Run functions (2nd Gen) と Cloud Run の関係

ここでデプロイ先に指定される Cloud Run functions (2nd Gen) は、内部的に Cloud Run を基盤として利用しています。
そのため、2nd Gen でデプロイされた関数は Cloud Run サービスとしても管理・実行されます。

この関係性について、詳しくは以前私が執筆したこちらの記事もご参照ください。

ただし、2nd Gen を利用する場合、後述するようにコンソールでの設定方法に注意点がありますので、その点も併せてご確認ください。

デプロイ方法
gcloud functions deploy before-create \
--region=asia-northeast1 \
--runtime nodejs20 \
--trigger-http \
--allow-unauthenticated \
--entry-point=beforeCreate \
--ingress-settings=internal-and-gclb

Ingress 設定 (--ingress-settings) に関する考慮事項

gcloud functions deploy コマンドで --ingress-settings を指定しない場合、デフォルトでは Ingress 設定が「すべて (all)」となり、インターネットからのすべてのリクエストを受け付けます。 (公式ドキュメントで案内される方式)

しかし、ブロッキング関数は Identity Platform, Firebase Authentication with Identity Platform の内部サービスからのみ呼び出されるため、セキュリティの観点からはアクセス元を限定することが望ましいです。

そこで、今回は --ingress-settings=internal-and-gclb を指定し、Ingress 設定を「内部トラフィックと外部アプリケーションロードバランサからのトラフィックのみを許可」に設定しました。

cloud-run
Cloud Run の Ingress 設定

「内部」トラフィックの定義とブロッキング関数トリガー

Cloud Run (Cloud Run functions 2nd Gen の基盤) における「内部」トラフィックの定義は以下の通りです。

同じプロジェクト内または VPC Service Controls 境界内にある Cloud Scheduler、Cloud Tasks、Eventarc、Pub/Sub、合成モニター (稼働時間チェックを含む) 、Workflow からのリクエストは「内部」として認識されます。

ブロッキング関数の beforeCreatebeforeSignIn トリガーが、Identity Platform からの呼び出しとしてこの「内部」トラフィックに該当するかどうかが重要になります。

結論、ブロッキング関数のトリガーは Identity Platform からの内部的な呼び出しとして扱われるため、Ingress 設定を internal-and-gclb に限定しても問題なく動作します。

これにより、不必要な外部からのアクセスを遮断し、よりセキュアな構成を実現できます。

4. Firebase Authentication with Identity Platform コンソールにて設定

ブロッキング関数は、通常のトリガー経由の Cloud Run functions や Eventarc 経由の Cloud Run とは異なり、Firebase Authentication with Identity Platform のコンソールから明示的に設定を行う必要があります。

firebase-auth
Firebase コンソール > Authentication コンソール > 設定メニュー > ブロッキング関数

まとめ

本記事では、Firebase Authentication with Identity Platform のブロッキング関数 (Blocking Functions) を、特に外部 IdP 連携の文脈で活用する方法を紹介しました。

ブロッキング関数を用いることで、外部 IdP から提供される情報に基づいた認証フローのカスタマイズや、独自のセキュリティ要件への対応が可能になります。これにより、開発者は認証ロジックの実装・管理負担を軽減しつつ、より柔軟でセキュアな認証基盤を構築できます。

紹介したドメイン制限や未承認ユーザーのブロックといったユースケースだけでなく、カスタムクレームの付与や IP アドレス制限など、様々なシナリオに応用可能です。

ぜひブロッキング関数を活用し、アプリケーションの認証体験とセキュリティ向上にお役立てください。

Discussion