---
title: "Firebase Authenticationとは？料金・認証方式・導入手順とMFA・IDトークン検証【2026年10月】"
url: "https://www.issoh.co.jp/tech/details/5921/"
published: 2025-03-19
updated: 2026-10-21
categories: ["Firebase"]
publisher: "株式会社一創"
---

# Firebase Authenticationとは？料金・認証方式・導入手順とMFA・IDトークン検証【2026年10月】

Firebase Authenticationは、メールアドレスとパスワード、Google・Appleなどのソーシャルログイン、電話番号（SMS）、匿名ログインを、クライアントSDKと認証バックエンドのセットで提供するFirebaseの認証サービスです（[公式ドキュメント](https://firebase.google.com/docs/auth)）。ユーザーごとに一意の `uid` と、1時間有効なIDトークン（JWT）を発行します。FirestoreやCloud Storageのセキュリティルール、自前のAPIサーバーは、このトークンで「誰のリクエストか」を判定します。JWTそのものの構造と署名検証の考え方は[JWTとは？構造・署名検証の仕組みとセッション・OAuth/OIDCとの違い](/tech/details/13453/)で解説しています。

この記事では、MAU課金が掛かる条件とSMSの単価、Web SDKでの実装、SMS・TOTPによる多要素認証（MFA）、Node.jsとPythonでのIDトークン検証、セッションCookieまでを扱います。数値と仕様は2026年10月21日時点の公式ドキュメントで確認し、コードは firebase 13.0.0・firebase-admin 14.5.0（Node.js）・firebase-admin 7.7.0（Python）を firebase-tools 15.34.0 のAuthエミュレーターで実行した結果を添えています。

## まとめ：Firebase Authenticationの料金・実装・運用で押さえる要点

- **料金**：メール・ソーシャル・匿名ログインは、Identity Platformにアップグレードしていなければ従量課金の対象外です。アップグレード後のBlazeでは、メール・ソーシャルなどのTier 1は5万MAUまで無料で、超過分は段階料金です。最初の超過帯は1MAUあたり0.0055ドルです。SAML・OIDCのTier 2は50MAUまで無料で、超過分は1MAUあたり0.015ドルです。SparkではMAU課金の代わりに日次上限が適用されます。
- **SMS**：電話番号認証とMFAのSMSは従量課金のBlazeプラン専用で、日本宛ては1通0.03ドルです。新規プロジェクトは送信を許可する地域が空の状態で始まります。
- **Sparkプランの落とし穴**：パスワードリセットメールは1日150通、メールリンクでのログインは1日5通が上限です。
- **MFA**：SMSとTOTPを第2要素に使えますが、Identity Platformへのアップグレードとメールアドレスの確認が前提です。サーバー側はIDトークンの `firebase.sign_in_second_factor` で、2要素目を通ったログインかどうかを判定できます。
- **トークン**：IDトークンの有効期限は1時間です。サーバー側はNode.jsなら `verifyIdToken(token, true)`、Pythonなら `verify_id_token(token, check_revoked=True)` で、失効したトークンも拒否できます。
- **Webの注意点**：`signInWithRedirect` は、Chrome M115以降などサードパーティストレージを分割するブラウザで、対策の設定をしないと動きません。
- **エラー処理**：2023年9月15日以降に作成したプロジェクトでは、パスワードの誤りも未登録も `auth/invalid-credential` にまとめて返されます。エミュレーターは個別のコードを返すので、ここだけは本番と挙動が違います。
- **SMSを送らない番号確認**：Firebase Phone Number Verificationは2026年10月時点で導入手順がAndroid向けだけで、対応キャリアの一覧に日本は入っていません。国内向けアプリでは当面SMSが主な手段です。

## 認証サービスの役割と、上位版Identity Platformへ移すと増える機能

Firebase Authenticationが担うのは「本人確認（認証）」までです。ログインに成功すると、SDKはIDトークンとリフレッシュトークンを受け取り、IDトークンの期限が近づくと自動で更新します。どのデータを読めるかという「認可」は、Firestoreのセキュリティルールや自前のサーバーが、トークンに入った `uid` やカスタムクレームを見て判断します。

上位版として、Google Cloudの **Identity Platform** があります。Firebaseコンソールから「Firebase Authentication with Identity Platform」へアップグレードすると、次の機能を使えるようになります。アップグレード後も既存の認証コードはそのまま動きますが、MFAやSAML・OIDCをアプリに組み込むには追加の実装が要ります。

| 機能                     | Firebase Authentication | アップグレード後 |
| ---------------------- | ----------------------- | -------- |
| メール・ソーシャル・電話・匿名・カスタム認証 | ○                       | ○        |
| 多要素認証（SMS・TOTP）        | ×                       | ○        |
| ブロッキング関数               | ×                       | ○        |
| SAML・OpenID Connect    | ×                       | ○        |
| マルチテナント                | ×                       | ○        |
| 匿名アカウントの自動削除（30日）      | ×                       | ○        |
| 稼働率SLA                 | なし                      | 99.95%   |

アップグレードすると料金体系と利用上限が変わります。Blazeでは次章のMAU課金が適用され、Sparkでは課金の代わりに日次アクティブユーザー数の上限が適用されます。MFAやSAMLが要件に無いなら、アップグレードしないまま始めたほうが請求の見通しを立てやすくなります。

## 対応する認証方式の一覧と、方式ごとに外部で必要になる事前設定

どの方式も、コンソールの［Security］→［Authentication］→［Sign-in method］タブでプロバイダを有効にしてから使います。外部サービスのログインでは、相手側の開発者コンソールでアプリを登録し、Firebaseが表示するリダイレクトURI（`https://プロジェクトID.firebaseapp.com/__/auth/handler`）を登録する作業が加わります。

| 方式              | 外部での事前準備                | 注意点                   |
| --------------- | ----------------------- | --------------------- |
| メール／パスワード       | 不要                      | 既定の最小長は6文字（ポリシーで変更可能） |
| メールリンク（パスワードレス） | 不要                      | Sparkは1日5通まで          |
| Google          | 不要                      | コンソールで有効化のみ           |
| Apple           | Apple Developer Program | Service IDと鍵の登録       |
| Facebook・GitHub | 各開発者コンソール               | App IDとシークレット         |
| X（Twitter）      | X Developer Portal      | Firebase上の名称はTwitter  |
| Microsoft・Yahoo | 各開発者コンソール               | OAuthProviderで指定      |
| 電話番号（SMS）       | 不要                      | Blaze必須・地域ポリシー        |
| 匿名              | 不要                      | 上限1億アカウント             |
| カスタム認証          | 自前の認証サーバー               | Admin SDKでトークン発行      |
| SAML・OIDC       | IdP側の設定                 | Identity Platform必須   |

ログイン画面を自作せずに済ませたい場合は、公式のUIライブラリがあります。安定版v6とベータのv7の違いは[FirebaseUIとは？Web認証UIの使い方とv6/v7の選び方](/tech/details/3397/)で扱っています。

## 料金：無料で使える範囲とMAU課金の条件、SMSとメール送信の上限

「Firebase Authenticationは無料か」への答えは、Identity Platformにアップグレードしているかどうかで変わります。[Firebaseの料金表](https://firebase.google.com/pricing)では、アップグレードしていない認証（電話番号以外）は、SparkでもBlazeでも従量課金の行を持ちません。

### Identity Platformにアップグレードした後のMAU単価と計算例

アップグレード後のBlazeプランでは、その月に1回でもログインしたユーザー数（MAU）に応じて、認証方式ごとの無料枠を超えた分が課金されます。[Identity Platformの料金表](https://cloud.google.com/identity-platform/pricing)（2026年10月21日時点）は次のとおりです。

| 区分                      | MAU         | 1MAUあたり  |
| ----------------------- | ----------- | -------- |
| Tier 1（メール・電話・匿名・ソーシャル） | 0〜5万        | 無料       |
| Tier 1                  | 5万〜10万      | 0.0055ドル |
| Tier 1                  | 10万〜100万    | 0.0046ドル |
| Tier 1                  | 100万〜1,000万 | 0.0032ドル |
| Tier 1                  | 1,000万以上    | 0.0025ドル |
| Tier 2（SAML・OIDC）       | 0〜50        | 無料       |
| Tier 2                  | 50以上        | 0.015ドル  |

たとえばMAUが8万なら、無料枠を超えた3万MAU×0.0055ドルで月165ドルです。匿名ユーザーは、自動削除を有効にしていればMAUに数えられません。支払い情報を登録していないSparkプランのままアップグレードした場合は、Tier 1が1日3,000アクティブユーザー、Tier 2が1日2ユーザーまでに制限されます。Firebase全体の無料枠と超過単価は[Firebaseの料金｜無料枠とBlaze超過単価の実額](/tech/details/16672/)にまとめています。

### Sparkのまま本番公開すると当たるSMSとメール送信の上限

見落としやすいのが送信系の上限です。[Firebase Authenticationの上限](https://firebase.google.com/docs/auth/limits)のページに載っている値のうち、Sparkプランのまま本番公開すると、ユーザー数が少なくても当たるのが次の項目です。

| 操作            | Spark       | Blaze      |
| ------------- | ----------- | ---------- |
| 確認メール（アドレス確認） | 1,000通／日    | 100,000通／日 |
| パスワードリセットメール  | 150通／日      | 10,000通／日  |
| メールリンクでのログイン  | 5通／日        | 25,000通／日  |
| SMS（確認コード）    | 送信不可        | 3,000通／日   |
| 新規アカウント作成     | IPあたり100件／時 | 同左         |

SMSの単価は宛先の国で決まり、日本宛ては1通0.03ドル、米国宛ては0.01ドルです。Identity Platformの料金表では、1日あたり最初の10通は課金されず、電話番号でのログインだけでなくMFAの確認コードも同じ単価で数えられます。Identity Platformにアップグレードすると、1日3,000通の上限は外れます。メールリンク認証を実際のメールで1日5通を超えて試すならBlazeが必要です。エミュレーターならメールは送られず、ログイン用のリンクがターミナルに出力されるので、送信上限を気にせず動作を確認できます。

## 導入手順：プロバイダの有効化とWebログインの実装

プロジェクトの作成とSDKの初期化がまだの場合は、[Firebaseの導入手順｜プロジェクト作成からSDK導入と初回デプロイまで](/tech/details/16678/)を先に済ませてください。ここでは、認証に固有の作業だけを扱います。

### コンソールでのプロバイダ有効化と承認済みドメインの設定

1. コンソールの［Security］→［Authentication］を開き、初回は［始める］を押す。
2. ［Sign-in method］タブで使うプロバイダを有効にする。
3. ［Settings］タブの承認済みドメインに、本番のドメインを追加する（Firebase Hostingの既定ドメインは登録済みですが、2025年4月28日以降に作成したプロジェクトでは `localhost` は既定で含まれません。ローカル開発で必要な場合は手動で追加します）。

承認済みドメインに無いドメインからポップアップやリダイレクトでログインすると、`auth/unauthorized-domain` で失敗します。

### Web SDKのモジュラー形式によるメール／パスワード認証の実装

npmの `firebase` 13.0.0（2026年10月7日公開）で書き、Authエミュレーターで動かしたコードです。関数を個別にimportするモジュラー形式で、`firebase.auth()` のような旧形式は使いません。13.0.0の破壊的変更は[JavaScript SDKのリリースノート](https://firebase.google.com/support/release-notes/js)上ではAI Logicに限られ、同梱の `@firebase/auth` は1.13.7です。12系で書いた認証コードは書き換えずに動きます。

```
// auth-client.js
import { initializeApp } from "firebase/app";
import {
  getAuth, onAuthStateChanged,
  createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut,
} from "firebase/auth";

const app = initializeApp({
  apiKey: "YOUR_API_KEY",
  authDomain: "your-project.firebaseapp.com",
  projectId: "your-project",
});
export const auth = getAuth(app);
auth.languageCode = "ja"; // 確認メール・SMS・reCAPTCHAの言語

export async function signUp(email, password) {
  const { user } = await createUserWithEmailAndPassword(auth, email, password);
  return user;
}

export async function signIn(email, password) {
  const { user } = await signInWithEmailAndPassword(auth, email, password);
  return user;
}

// ログイン状態の変化（ページ再読み込み時の復元を含む）を受け取る
onAuthStateChanged(auth, (user) => {
  console.log(user ? `signed in: ${user.uid}` : "signed out");
});

export { signOut };
```

ページを開いた直後の `auth.currentUser` は、ログイン状態の復元が終わるまで `null` です。ログイン済みかどうかの判定は、`onAuthStateChanged` の最初の呼び出しを待ってから行います。

### Googleログインの追加とsignInWithRedirectのブラウザ制約への対処

```
import { GoogleAuthProvider, signInWithPopup } from "firebase/auth";
import { auth } from "./auth-client.js";

const provider = new GoogleAuthProvider();
const { user } = await signInWithPopup(auth, provider);
```

モバイルブラウザでポップアップを避けたいときに使う `signInWithRedirect` には落とし穴があります。この方式は、アプリとは別オリジンの `authDomain` をiframeで読み込んで結果を受け渡すため、サードパーティストレージを分割するブラウザでは結果を受け取れません。[公式のベストプラクティス](https://firebase.google.com/docs/auth/web/redirect-best-practices)は、2024年6月24日以降のChrome M115以降で対策を必須とし、Firefox 109以降とSafari 16.1以降では以前から必要だったとしています。

- **Firebase Hostingのfirebaseapp.comサブドメインで配信している**：影響なし。
- **Firebase Hostingで独自ドメインかweb.appを使っている**：`authDomain` を配信中のドメインに変更し、各プロバイダのリダイレクトURIに `https://配信ドメイン/__/auth/handler` を追加する。
- **Firebase以外で配信している**：`signInWithPopup` へ切り替える（公式の選択肢2）、`/__/auth/` 配下へのリクエストをfirebaseapp.comへプロキシする（選択肢3）、ログイン用のヘルパーコードを自ドメインに置く（選択肢4）のいずれか。選択肢5はプロバイダのSDKで個別にログインする方法で、公式も実装が複雑になると注記しています。

## SDKを使えない環境でのREST APIログインとIDトークンの取得

CLIツールや負荷試験、SDKの無い言語からFirebase Authenticationを使う場合は、[Firebase Auth REST API](https://firebase.google.com/docs/reference/rest/auth)を直接呼びます。メール／パスワードでのログインは `accounts:signInWithPassword` へのPOSTで、`returnSecureToken` を `true` にするとIDトークンとリフレッシュトークンが返ります。`key` に入れるのは、プロジェクトのWeb APIキーです。

```
# メール/パスワードでログインし、IDトークンとリフレッシュトークンを受け取る
curl -s "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${API_KEY}" \
  -H "Content-Type: application/json" \
  --data-binary "$(python3 -c 'import json,sys; print(json.dumps({"email":sys.argv[1],"password":sys.argv[2],"returnSecureToken":True}))' "$EMAIL" "$PASSWORD")"

# 期限が切れたら、リフレッシュトークンで新しいIDトークンに交換する
curl -s "https://securetoken.googleapis.com/v1/token?key=${API_KEY}" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data "grant_type=refresh_token&refresh_token=${REFRESH_TOKEN}"
```

ログインの応答には `idToken`・`refreshToken`・`expiresIn`（秒数）・`localId`（`uid`）が入ります。交換側の応答はキー名がスネークケース（`id_token`・`refresh_token`・`user_id`）に変わるので、パース処理を共通化するときは注意してください。リフレッシュトークンは、APIキーと同じプロジェクトのものでないと `PROJECT_NUMBER_MISMATCH` で拒否されます。MFAを登録したユーザーの場合、同じ呼び出しでも `idToken` は返りません（後述のMFAの章で扱います）。

新規登録は `accounts:signUp`、カスタムトークンからの交換は `accounts:signInWithCustomToken` と、エンドポイント名がSDKの関数名にほぼ対応しています。Web APIキーはクライアントに埋め込む前提の識別子で秘密情報ではありませんが、Google CloudコンソールでAPIの制限を掛けておくと、他のAPIを勝手に呼ばれる余地を減らせます。

## IDトークンの有効期限とAdmin SDKによるサーバー側検証

公式ドキュメント「[ユーザーセッションの管理](https://firebase.google.com/docs/auth/admin/manage-sessions)」によると、トークンの仕様は次のとおりです。

- **IDトークン**：有効期限は1時間。期限が近づくとSDKがリフレッシュトークンで自動更新する。
- **リフレッシュトークン**：期限はなく、ユーザーの削除、無効化、パスワードやメールアドレスの変更といった大きなアカウント変更があったときだけ失効する。Admin SDKの `revokeRefreshTokens()` で明示的に失効させることもできる。

エミュレーターで `getIdTokenResult()` を実行すると、`issuedAtTime` から `expirationTime` までは3,600秒でした。自前のAPIに送るときは `Authorization: Bearer` ヘッダーに載せ、サーバー側でAdmin SDKを使って検証します。手順は公式の[IDトークンを検証する](https://firebase.google.com/docs/auth/admin/verify-id-tokens)のページに沿っています。

### Node.jsのAdmin SDKによるIDトークン検証

firebase-admin 14.5.0（Node.js 22以上）での書き方です。14.0.0で `admin.auth()` のような名前空間形式のAPIは削除されたため、`firebase-admin/auth` から `getAuth` をimportします。古い記事のコードをそのまま貼ると `admin.auth is not a function` で止まります。

```
// server.js（Node.js 22以上）
import { initializeApp } from "firebase-admin/app";
import { getAuth } from "firebase-admin/auth";

initializeApp(); // Google Cloud上ではADC（サービスアカウント）で初期化される

export async function verifyRequest(authorizationHeader) {
  const m = /^Bearer (.+)$/.exec(authorizationHeader ?? "");
  if (!m) throw new Error("missing bearer token");
  // 第2引数 true で、revokeRefreshTokens() 後のトークンも拒否する
  const decoded = await getAuth().verifyIdToken(m[1], true);
  return { uid: decoded.uid, role: decoded.role ?? "user" };
}
```

管理者ロールのような権限は `setCustomUserClaims()` でIDトークンに載せられます（[カスタムクレームの公式ガイド](https://firebase.google.com/docs/auth/admin/custom-claims)によればJSONで1,000バイト以内）。ただし、付与しても発行済みのトークンには入りません。エミュレーターでも、付与直後の `getIdTokenResult()` では `role` が未定義で、`getIdTokenResult(true)` で強制更新した後に初めて値が入りました。すぐに反映させたい場合はクライアント側で強制更新し、急がないなら通常のトークン更新か再ログインを待てば反映されます。Admin SDKの初期化方法やユーザー管理APIは[Firebase Admin SDKとは？クライアントSDKとの違いと導入・初期化・ユーザー管理](/tech/details/3451/)で解説しています。

### Python（Flask）でのIDトークン検証と失効・時刻差への対応

Pythonでは `firebase-admin` 7.7.0（2026年9月23日公開・Python 3.9以上）の `auth.verify_id_token()` を使います。`check_revoked=True` で失効と無効化を確認し、`clock_skew_seconds` でサーバーの時計のずれを許容します。

```
# app.py（Flask 3.1・firebase-admin 7.7.0）
from flask import Flask, abort, jsonify, request
import firebase_admin
from firebase_admin import auth

firebase_admin.initialize_app()  # Google Cloud上ではADC（サービスアカウント）で初期化される
app = Flask(__name__)


def verify_request():
    header = request.headers.get("Authorization", "")
    if not header.startswith("Bearer ") or not header[7:].strip():
        abort(401)
    try:
        # check_revoked=True で失効・無効化を確認し、時計のずれは10秒まで許容する（上限60秒）
        return auth.verify_id_token(header[7:], check_revoked=True, clock_skew_seconds=10)
    except (auth.InvalidIdTokenError, auth.UserDisabledError):
        abort(401)


@app.get("/me")
def me():
    claims = verify_request()
    return jsonify(uid=claims["uid"], second_factor=claims["firebase"].get("sign_in_second_factor"))
```

Authエミュレーターに向けて（`FIREBASE_AUTH_EMULATOR_HOST` を設定して）このアプリを起動し、5つのケースを試した結果です。

| 送ったもの                         | 応答  | 捕まえた例外              |
| ----------------------------- | --- | ------------------- |
| 有効なIDトークン                     | 200 | なし                  |
| Authorizationヘッダーなし           | 401 | （ヘッダー判定）            |
| JWTでない文字列                     | 401 | InvalidIdTokenError |
| 無効化したユーザーのトークン                | 401 | UserDisabledError   |
| revoke\_refresh\_tokens後のトークン | 401 | RevokedIdTokenError |

`RevokedIdTokenError` と `ExpiredIdTokenError` は `InvalidIdTokenError` の子クラスですが、`UserDisabledError` は別系統です。明示して捕まえないと、無効化したユーザーのリクエストが500エラーになります。`clock_skew_seconds` に61を渡すと `ValueError`（`Must be between 0 and 60, inclusive`）になるため、60秒を超えるずれはサーバーの時刻同期で直します。

## サーバー描画（SSR）アプリでのセッションCookieの発行・検証

IDトークンは1時間で切れ、クライアントSDKが更新する前提の仕組みです。ページをサーバーで描画するアプリ（Express、Next.jsのサーバー側処理など）では、リクエストごとにIDトークンを送らせるより、ログイン直後にサーバーで**セッションCookie**へ交換する方式が扱いやすくなります。[セッションCookieの管理](https://firebase.google.com/docs/auth/admin/manage-cookies)の仕様では、有効期限は5分〜2週間の範囲で指定でき、Cookieにはカスタムクレームを含めてIDトークンと同じクレームが入ります。

```
// session-server.js（express・cookie-parser・firebase-admin を使用）
import express from "express";
import cookieParser from "cookie-parser";
import { initializeApp } from "firebase-admin/app";
import { getAuth } from "firebase-admin/auth";

initializeApp();
const app = express();
app.use(express.json());
app.use(cookieParser());

const EXPIRES_IN = 5 * 24 * 60 * 60 * 1000; // 5日（ミリ秒。5分〜2週間で指定）

app.post("/sessionLogin", async (req, res) => {
  // CSRF対策：Cookieとリクエスト本文のトークンが一致しなければ拒否する
  if (!req.body.csrfToken || req.body.csrfToken !== req.cookies.csrfToken) {
    return res.status(401).send("invalid csrf token");
  }
  try {
    const idToken = String(req.body.idToken);
    const decoded = await getAuth().verifyIdToken(idToken);
    // 直近5分以内にログインしたトークンだけをCookieへ交換する
    if (Date.now() / 1000 - decoded.auth_time > 5 * 60) {
      return res.status(401).send("recent sign-in required");
    }
    const session = await getAuth().createSessionCookie(idToken, { expiresIn: EXPIRES_IN });
    res.cookie("session", session, { maxAge: EXPIRES_IN, httpOnly: true, secure: true, sameSite: "lax" });
    res.json({ status: "ok" });
  } catch {
    res.status(401).send("unauthorized");
  }
});

app.get("/profile", async (req, res) => {
  try {
    // 第2引数 true で、失効・無効化されたユーザーのCookieを拒否する
    const claims = await getAuth().verifySessionCookie(req.cookies.session ?? "", true);
    res.json({ uid: claims.uid });
  } catch {
    res.redirect("/login");
  }
});

app.listen(3000);
```

公式ドキュメントのサーバー側処理を1ファイルにまとめた例です。実行にはexpress・cookie-parser・firebase-adminの導入に加え、CSRFトークンの生成・Cookieへの設定と、その値およびIDトークンを送信するクライアント側処理が必要です。公式はあわせて、Cookie発行後にクライアントSDKをログアウトさせ、ログイン状態を二重に残さないことも求めています。セッションCookieはFirestoreなどほかのFirebaseサービスの認証には使えないため、検証後の権限チェックはアプリが行います。

ログアウト時は `revokeRefreshTokens(uid)` を呼んでからCookieを消すと、他の端末に残ったCookieも `verifySessionCookie(cookie, true)` の時点で拒否されます。セッションIDの発行から失効までの設計全般は[セッション管理とは？セッションIDの発行から失効までの実装と保存先選定](/tech/details/16366/)で整理しています。

## Firestoreのrequest.authによる本人データへのアクセス制限

FirestoreをクライアントSDKから直接読み書きする構成では、Firebase Authenticationのログイン情報がルールの `request.auth` に入ります（[セキュリティルールとFirebase Authentication](https://firebase.google.com/docs/rules/rules-and-auth)）。次のルールは「自分の `uid` と同じIDのドキュメントだけ読み書きできる」という最小構成です。

```
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
  }
}
```

Firestoreエミュレーター（firebase 13.0.0）で試すと、ログイン中のユーザーは自分のドキュメントに書き込め、他人のIDのドキュメントへの書き込みとログアウト後の読み取りは `permission-denied` で拒否されました。MFAを通ったログインだけに書き込みを許すなら、条件に `request.auth.token.firebase.sign_in_second_factor != null` を足します。Firestore側の料金や設計は[Cloud Firestoreとは？NoSQLドキュメントDBの仕組み・料金・実装判断](/tech/details/15563/)で扱っています。

## 多要素認証（MFA）：SMS・TOTPの登録とログイン時の第2要素確認

Firebase AuthenticationのMFAは、Identity Platformへアップグレードしたプロジェクトでだけ使えます。第2要素はSMSとTOTP（認証アプリの6桁コード）の2種類で、公式手順は[SMSの多要素認証](https://firebase.google.com/docs/auth/web/multi-factor)と[TOTPの多要素認証](https://firebase.google.com/docs/auth/web/totp-mfa)のページにあります。

### MFAを有効にする前提条件と、SMS・TOTPそれぞれの有効化方法

- **メールアドレスの確認が必須**：他人のメールアドレスで登録して第2要素を足し、本来の持ち主を締め出す攻撃を防ぐためです。エミュレーターでも、未確認のユーザーが登録を始めると `UNVERIFIED_EMAIL : Need to verify email first before enrolling second factors.` で拒否されました。
- **1要素目に使えないプロバイダ**：電話番号認証、匿名認証、Apple Game Centerは対象外です（TOTPはカスタム認証トークンも対象外）。
- **SMSの有効化**：コンソールの［Sign-in method］タブの「Advanced」でSMS Multi-factor Authenticationをオンにし、SMS region policyで送信先の国を許可します。テスト用の電話番号を登録しておくと、開発中にSMSの送信制限に当たらずに済みます。
- **TOTPの有効化**：コンソールにスイッチは無く、Admin SDK（Node.js 11.6.0以上）かREST APIでプロジェクト設定を更新します。Web SDKは9.19.1以上のモジュラー形式が必要です。

```
// enable-totp.js：TOTPを第2要素として有効にする（一度だけ実行）
import { initializeApp } from "firebase-admin/app";
import { getAuth } from "firebase-admin/auth";

initializeApp();
await getAuth().projectConfigManager().updateProjectConfig({
  multiFactorConfig: {
    providerConfigs: [{
      state: "ENABLED",
      // 前後の時間枠（通常30秒）をいくつまで受け入れるか。0〜10、既定は5
      totpProviderConfig: { adjacentIntervals: 5 },
    }],
  },
});
```

### Web SDKでのTOTP登録とリゾルバによる第2要素の確認

```
import {
  multiFactor, TotpMultiFactorGenerator, getMultiFactorResolver,
  signInWithEmailAndPassword,
} from "firebase/auth";
import { auth } from "./auth-client.js";

// 登録：直近にログインし直したユーザーで実行する
export async function startTotpEnrollment(user) {
  const session = await multiFactor(user).getSession();
  const secret = await TotpMultiFactorGenerator.generateSecret(session);
  // QRコード用のURIと、QRを読めない認証アプリ向けの秘密鍵を両方表示する
  return { secret, uri: secret.generateQrCodeUrl(user.email, "MyApp"), key: secret.secretKey };
}
export async function finishTotpEnrollment(user, secret, code) {
  const assertion = TotpMultiFactorGenerator.assertionForEnrollment(secret, code);
  await multiFactor(user).enroll(assertion, "認証アプリ");
}

// ログイン：1要素目が通ると auth/multi-factor-auth-required が投げられる
export async function signInWithMfa(email, password, askCode) {
  try {
    return await signInWithEmailAndPassword(auth, email, password);
  } catch (e) {
    if (e.code !== "auth/multi-factor-auth-required") throw e;
    const resolver = getMultiFactorResolver(auth, e);
    const hint = resolver.hints.find((h) => h.factorId === TotpMultiFactorGenerator.FACTOR_ID);
    if (!hint) throw e; // SMSだけ登録しているユーザーは PhoneMultiFactorGenerator で処理する
    const assertion = TotpMultiFactorGenerator.assertionForSignIn(hint.uid, await askCode());
    return resolver.resolveSignIn(assertion);
  }
}
```

このコードは公式ガイドのAPIに沿って書いたものです。AuthエミュレーターはTOTPに対応しておらず、登録を始めると `INVALID_ARGUMENT : ((Missing phoneEnrollmentInfo.))` が返るため、動作確認にはIdentity Platformへアップグレードした検証用プロジェクトが要ります。SMSの第2要素は、Webでは `RecaptchaVerifier` と `PhoneAuthProvider.verifyPhoneNumber()` を組み合わせて登録します（ブラウザ外のNode.jsでは `auth/operation-not-supported-in-this-environment` で止まります）。複数の要素を登録したユーザーが最後に追加した要素を外すと、`auth/user-token-expired` でログアウトされます。

### sign\_in\_second\_factorのサーバー側検証によるMFA必須操作の制御

クライアントでMFAを要求しても、サーバーが「どのログイン方法で発行されたトークンか」を見なければ、MFAを登録していないユーザーのトークンで同じAPIを呼べてしまいます。SMSのMFAの登録からログインまでをREST APIでエミュレーターに対して実行すると、次の挙動を確認できました。

- MFA登録後の `accounts:signInWithPassword` の応答は `kind・registered・localId・email・mfaPendingCredential・mfaInfo` だけで、`idToken` が無い。`mfaInfo` の電話番号は `+********5678` のように伏せられている。
- `v2/accounts/mfaSignIn:start` と `mfaSignIn:finalize` に確認コードを渡して、初めて `idToken` と `refreshToken` が返る。誤ったコードは `INVALID_CODE`。
- このIDトークンをAdmin SDKで検証すると、`firebase` クレームに `"sign_in_second_factor":"phone"` と `second_factor_identifier`（登録ID）が入る。MFAを通っていないトークンにはこのキーが無い。

```
// 送金・メールアドレス変更など、MFAを必須にしたいAPIの前で呼ぶ
export async function requireSecondFactor(idToken) {
  const decoded = await getAuth().verifyIdToken(idToken, true);
  if (!decoded.firebase.sign_in_second_factor) {
    throw new Error("second factor required"); // 403を返し、MFAの登録画面へ誘導する
  }
  return decoded;
}
```

REST APIでログインしているCLIやテストスクリプトは、MFAを登録したユーザーでは `idToken` を取れなくなります。テスト用アカウントにはMFAを登録しないか、`mfaPendingCredential` を受けて2要素目を送る処理を足してください。

## 電話番号認証（SMS）の本番設定と、SMSを使わない番号確認の選択肢

電話番号認証は、メール認証と比べて事前の設定項目が多く、費用も送信数に比例します。Webでの実装手順は[JavaScriptでの電話番号認証](https://firebase.google.com/docs/auth/web/phone-auth)のページにあります。

### SMS認証を本番で有効にする前に確認する4つの設定項目

- **Blazeプラン必須**：Sparkでは確認コードのSMSを送れません。
- **SMSの送信地域ポリシー**：新規プロジェクトは許可地域が空の状態で始まるため、［Security］→［Authentication］→［Settings］の「SMS region policy」で日本（JP）などを許可しないと送信されません。
- **reCAPTCHA**：Webでは `RecaptchaVerifier` の設置が必須で、`localhost` は電話認証用のドメインとして使えません。開発中はテスト用の電話番号とコードをコンソールで登録して検証します。
- **SMSの不正送信**：高単価の国へ大量に送らせる攻撃を受けると、請求は送信数に比例して増えます。日本向けに限定したサービスなら、SMSの許可地域を日本に絞れば他地域への不正送信は防げます。日本宛ての不正送信には、IPあたりの送信上限（1分50通・1時間500通）に頼らず、別途対策が必要です。

Webアプリ全体のボット対策として、FirebaseのバックエンドへのリクエストをApp Checkで検証する方法もあります（[Firebase App Checkとは｜プロバイダ選定と監視から強制への移行](/tech/details/16691/)）。

### SMS以外の選択肢：Phone Number VerificationとTwilio Verifyの比べ方

Googleは、SMSを送らずに携帯キャリアから端末のSIMに割り当てられた番号を取得する [Firebase Phone Number Verification](https://firebase.google.com/docs/phone-number-verification)（PNV）を提供しています。確認コードが無いのでフィッシングで盗まれる心配がなく、ユーザーの同意から通常1〜3秒で署名付きトークンが返ります。ただし、2026年10月時点では国内向けの選択肢になりません。

- **導入手順はAndroid向けだけ**：公式の導入ガイドはAndroid（`com.google.firebase:firebase-pnv` 17.0.0）のみで、iOSとWebの手順は公開されていません。
- **対応キャリアに日本が無い**：[料金と対応キャリアのページ](https://firebase.google.com/docs/phone-number-verification/pricing)（2026年10月20日更新）の対応地域は、英国・ドイツ・スペインなど欧州とインドネシア・マレーシア・パキスタンの12地域です。
- **Firebase Authenticationのログインには直接使えない**：公式ドキュメントはAuthenticationでの対応を開発中（Beta）としており、現状はPNVのトークンをカスタム認証でFirebaseのトークンへ交換する実装が要ります。
- **課金は番号の取得に成功したときだけ**：単価は地域と月間件数で決まり、月間地域別件数が99,999件までの場合は、スペインで1件0.0312ドル、インドネシアで0.1080ドルです。100万件以上では、それぞれ0.0289ドル、0.1000ドルになります。SIMを使わないテストモードは課金アカウントなしで試せます。

確認コードのSMSを外部サービスで送るなら、比較対象は [Twilio Verify](https://www.twilio.com/en-us/verify/pricing) です。料金は認証の成功1件につき0.05ドルに、チャネルごとの送信料（SMSは米国宛て1通0.0083ドルなど）が加わる体系で、送信1通ごとに課金するFirebase（日本宛て0.03ドル）とは数え方が違います。Twilio Verifyでログインまで完結させるには、確認に成功した番号でFirebaseのカスタムトークンを発行する処理を自前で持つことになります。Firebase AuthenticationをID基盤にしているなら、標準の電話番号認証で始め、SMSの請求額や到達率が問題になってから比べ直すほうが手戻りは少なく済みます。

## エラーメッセージの日本語化とメール列挙保護によるエラーコードの変更

`auth.languageCode = "ja"` を設定すると、Firebaseが送る確認メール・パスワード再設定メール・SMS・reCAPTCHAと、対応しているプロバイダのログイン画面が日本語になります。端末やブラウザの言語設定に合わせたい場合は `useDeviceLanguage(auth)` を使います。メールの文面を編集する画面は［Security］→［Authentication］→［Templates］です。SDKが投げるエラーの `message` は、`languageCode` を `"ja"` にしてもエミュレーターで `Firebase: Error (auth/user-not-found).` という英語のままでした。画面に出す文言は `error.code` から自前で対応付けます。

```
const MESSAGES = {
  "auth/invalid-credential": "メールアドレスまたはパスワードが違います",
  "auth/wrong-password": "メールアドレスまたはパスワードが違います",
  "auth/user-not-found": "メールアドレスまたはパスワードが違います",
  "auth/email-already-in-use": "このメールアドレスは登録済みです",
  "auth/weak-password": "パスワードが設定された要件を満たしていません",
  "auth/too-many-requests": "試行回数が多すぎます。時間をおいて再度お試しください",
  "auth/multi-factor-auth-required": "確認コードを入力してください",
};
export const toMessage = (e) =>
  MESSAGES[e.code] ?? `ログインに失敗しました（${e.code}）`;
```

パスワード誤りと未登録を同じ文言にしているのには理由があります。2023年9月15日以降に作成したプロジェクトでは**メール列挙保護**が既定で有効で（[Identity Platformのメール列挙保護](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection)）、どちらの場合もサーバーは `INVALID_LOGIN_CREDENTIALS` を返し、Web SDKはこれを `auth/invalid-credential` に変換します（13.0.0に同梱の `@firebase/auth` 1.13.7のソースで確認）。`fetchSignInMethodsForEmail` も登録済みのログイン方法を返さなくなりました。

一方、Authエミュレーター（firebase-tools 15.34.0）で試すと、パスワード誤りは `auth/wrong-password`、未登録は `auth/user-not-found` と個別のコードが返りました。エミュレーターで通った「未登録なら新規登録へ誘導する」分岐は、本番では動きません。エラーコードで登録済みかどうかを判定する設計は避けてください。

## 匿名認証で発行したuidを、本登録時に同じアカウントへ引き継ぐ方法

会員登録の前にカートやお気に入りを使わせたい場合は、[匿名認証](https://firebase.google.com/docs/auth/web/anonymous-auth)で `uid` を先に発行し、本登録時に `linkWithCredential` で同じアカウントへメールやGoogleの認証情報を結び付けます。

```
import { signInAnonymously, linkWithCredential, EmailAuthProvider } from "firebase/auth";

const { user } = await signInAnonymously(auth);
// ……匿名のまま uid をキーにデータを保存……
const credential = EmailAuthProvider.credential(email, password);
const linked = await linkWithCredential(user, credential);
console.log(linked.user.uid === user.uid, linked.user.isAnonymous); // true false
```

エミュレーターで実行すると、`uid` はリンク前後で変わらず、`isAnonymous` は `false` になりました。`uid` をキーにしたFirestoreのデータをそのまま引き継げます。匿名アカウントの上限は1億件で、使われなくなったものは、Identity Platformにアップグレードして自動削除（作成から30日経過が対象）を有効にしない限り残り続けます。

## Authエミュレーターへの接続と本番ユーザーを作らない動作検証

本番のプロジェクトにテストユーザーを作らずに試すには、Local Emulator SuiteのAuthエミュレーターを使います（[Authenticationエミュレーターへの接続](https://firebase.google.com/docs/emulator-suite/connect%5Fauth)）。`npx firebase emulators:start --only auth` で起動し、クライアント側で接続先を切り替えます。次の接続処理は、先ほどの `auth-client.js` 内で `getAuth(app)` の直後に同期的に実行し、認証処理を呼び出す前に接続先を確定してください。

```
import { connectAuthEmulator } from "firebase/auth";
if (location.hostname === "localhost") {
  connectAuthEmulator(auth, "http://127.0.0.1:9099");
}
```

Admin SDK側は、Node.jsでもPythonでも環境変数 `FIREBASE_AUTH_EMULATOR_HOST=127.0.0.1:9099` で接続先が切り替わります。Androidのエミュレーター端末からはホストに `10.0.2.2` を指定します。SMSやメールは実際には送られず、確認コードはエミュレーターのログと `/emulator/v1/projects/プロジェクトID/verificationCodes` で確認でき、Webの電話認証ではreCAPTCHAの検証も省略されます。本番との違いは、前述のエラーコードの返し方と、TOTPのMFAに対応していない点です。CIへの組み込みは[Firebase Local Emulator Suiteとは｜ローカル再現の構成とCI組み込み](/tech/details/16682/)で扱っています。

## Firebase Authenticationの採用判断と、他の認証基盤と比べる条件

小〜中規模のWeb・モバイルアプリで、メールとソーシャルログインが中心なら、Firebase Authenticationは導入工数と費用の両面で有力な選択肢です。次の条件に当てはまる場合は、他の認証基盤と比べてから決めてください。

- **企業向けSAML・OIDCのユーザーが多い**：Tier 2は無料枠が50MAUで、超過分は1MAUあたり0.015ドル。Tier 1の約3倍の単価です。B2B向けの認証基盤として比べるなら、[Auth0の料金｜MAU課金の実額と無料枠を超えた後の増え方](/tech/details/17297/)も判断材料になります。
- **パスキー（WebAuthn）をすぐにログイン方式の中心に据えたい**：2026年10月時点で、公式ドキュメントのプロバイダ一覧とMFAの第2要素（SMS・TOTP）にパスキーは無く、公開版のWeb SDK 13.0.0（`@firebase/auth` 1.13.7）にもパスキーのAPIはありません。一方で、firebase-tools 15.21.0（2026年6月17日）の[リリースノート](https://github.com/firebase/firebase-tools/releases/tag/v15.21.0)にはAuthエミュレーターへのパスキーのモック追加が載っており、提供に向けた準備は進んでいます。提供を待てないなら、対応済みの認証基盤と比べるか、WebAuthnの検証を自前で実装してカスタム認証でつなぐ設計になります。
- **ユーザーデータをSQLで直接結合したい**：ユーザー情報はFirebaseの管理下にあり、自前のDBとは別に持つことになります。PostgreSQLの行レベルセキュリティと組み合わせるなら[Supabase Authとは：非対称JWT鍵とRLS連携で組む認証実装の設計判断](/tech/details/17245/)が比較対象です。サービス全体の比較は[SupabaseとFirebaseの違いを徹底比較｜料金・認証・どっちを選ぶか](/column/details/3002/)にまとめています。
- **AWS上でバックエンドを完結させたい**：IAMやAPI Gatewayとの統合を優先するなら[Amazon Cognito（コグニート）とは？料金・できること・使い方](/tech/details/3108/)を比較してください。

既存のモバイルアプリでは、2025年8月25日のFirebase Dynamic Links終了の影響も確認してください（[Dynamic Linksの非推奨に関するFAQ](https://firebase.google.com/support/dynamic-links-faq)）。影響を受けたのはモバイルのメールリンク認証、Androidの古いSDKでのOAuthフロー、Cordova向けOAuthの3つで、メールリンク認証はAndroid SDK 23.2.0以降（BoM 33.9.0以降）・iOS SDK 11.8.0以降への更新が要ります。移行の全体像は[Firebase Dynamic Linksの終了と移行](/tech/details/17054/)で扱っています。

会員基盤を既存システムから移す、社内IdPとのSSOやMFAを含めて設計する、といった案件では、認証方式の選定とユーザー移行の手順を、実装前に整理する必要があります。株式会社一創の[認証基盤・ID管理システム開発](https://www.issoh.co.jp/service/system/identity/)では、Firebase AuthenticationやIdentity Platformを含めた認証方式の選定から、既存ユーザーの移行、SSO・パスキー対応の実装までを支援しています。

## よくある質問

### Firebase Authenticationは無料ですか？

Identity Platformにアップグレードしていなければ、メール・ソーシャル・匿名ログインは無料です。アップグレード後のBlazeでは、Tier 1は月5万MAUまで無料で、最初の超過帯は1MAUあたり0.0055ドルです。SAML・OIDCのTier 2は月50MAUまで無料で、超過分は1MAUあたり0.015ドルです。SparkではTier 1が1日3,000人、Tier 2が1日2人までです。電話番号認証とMFAのSMSはどちらの場合もBlazeプランでの従量課金で、日本宛ては1通0.03ドルです。

### Firebase AuthenticationのIDトークンの有効期限はどのくらいですか？

1時間です。SDKがリフレッシュトークンを使って自動更新するので、アプリ側で再ログインさせる必要はありません。リフレッシュトークンに期限はなく、ユーザーの削除・無効化、パスワードやメールアドレスの変更、Admin SDKでの明示的な失効で無効になります。サーバー描画のアプリで長く使うセッションが必要なら、5分〜2週間で期限を決められるセッションCookieへ交換します。

### Firebase Authenticationを有効化するにはどうすればいいですか？

Firebaseコンソールで［Security］→［Authentication］を開いて［始める］を押し、［Sign-in method］タブで使うプロバイダを有効にします。本番ドメインは［Settings］タブの承認済みドメインに追加してください。SDKを使わない環境からは、REST APIの `accounts:signInWithPassword` でもログインできます。

### Firebase Authenticationで二段階認証（MFA）は使えますか？

Identity Platformにアップグレードすれば、SMSとTOTP（認証アプリ）を第2要素に使えます。メールアドレスを確認済みのユーザーしか登録できず、TOTPはコンソールではなくAdmin SDKかREST APIで有効にします。SMSの第2要素は電話番号認証と同じく1通ごとに課金されます。

### Firebase AuthenticationとFirestoreはどう連携しますか？

クライアントSDKでログインすると、Firestoreへのリクエストに認証情報が自動で付き、セキュリティルールの `request.auth.uid` で本人のデータだけを許可できます。サーバー側では、Admin SDKの `verifyIdToken` でIDトークンを検証し、Admin SDKによるFirestore操作にはセキュリティルールが適用されないことを踏まえて、アプリ側の認可に使う `uid` を取り出します。

## 関連記事

- [Firebase Admin SDKとは？クライアントSDKとの違いと導入・初期化・ユーザー管理【firebase-admin 14対応】](/tech/details/3451/)
- [FirebaseUIとは？Web認証UIの使い方とv6/v7の選び方を解説【2026年版】](/tech/details/3397/)
- [Firebaseの料金｜無料枠とBlaze超過単価の実額【2026年8月時点】](/tech/details/16672/)
- [Firebase Local Emulator Suiteとは｜ローカル再現の構成とCI組み込み【2026年8月時点】](/tech/details/16682/)
- [Supabase Authとは：非対称JWT鍵とRLS連携で組む認証実装の設計判断](/tech/details/17245/)

---

出典: [Firebase Authenticationとは？料金・認証方式・導入手順とMFA・IDトークン検証【2026年10月】](<https://www.issoh.co.jp/tech/details/5921/>)（株式会社一創）
