22 人が閲覧(直近 30 日) Firebase

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

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

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

この記事では、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の選び方で扱っています。

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

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

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

アップグレード後のBlazeプランでは、その月に1回でもログインしたユーザー数(MAU)に応じて、認証方式ごとの無料枠を超えた分が課金されます。Identity Platformの料金表(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超過単価の実額にまとめています。

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

見落としやすいのが送信系の上限です。Firebase Authenticationの上限のページに載っている値のうち、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導入と初回デプロイまでを先に済ませてください。ここでは、認証に固有の作業だけを扱います。

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

  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のリリースノート上では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で読み込んで結果を受け渡すため、サードパーティストレージを分割するブラウザでは結果を受け取れません。公式のベストプラクティスは、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を直接呼びます。メール/パスワードでのログインは 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によるサーバー側検証

公式ドキュメント「ユーザーセッションの管理」によると、トークンの仕様は次のとおりです。

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

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

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トークンに載せられます(カスタムクレームの公式ガイドによればJSONで1,000バイト以内)。ただし、付与しても発行済みのトークンには入りません。エミュレーターでも、付与直後の getIdTokenResult() では role が未定義で、getIdTokenResult(true) で強制更新した後に初めて値が入りました。すぐに反映させたい場合はクライアント側で強制更新し、急がないなら通常のトークン更新か再ログインを待てば反映されます。Admin SDKの初期化方法やユーザー管理APIはFirebase Admin SDKとは?クライアントSDKとの違いと導入・初期化・ユーザー管理で解説しています。

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の管理の仕様では、有効期限は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の発行から失効までの実装と保存先選定で整理しています。

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

FirestoreをクライアントSDKから直接読み書きする構成では、Firebase Authenticationのログイン情報がルールの request.auth に入ります(セキュリティルールとFirebase Authentication)。次のルールは「自分の 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の仕組み・料金・実装判断で扱っています。

多要素認証(MFA):SMS・TOTPの登録とログイン時の第2要素確認

Firebase AuthenticationのMFAは、Identity Platformへアップグレードしたプロジェクトでだけ使えます。第2要素はSMSとTOTP(認証アプリの6桁コード)の2種類で、公式手順はSMSの多要素認証とTOTPの多要素認証のページにあります。

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での電話番号認証のページにあります。

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とは|プロバイダ選定と監視から強制への移行)。

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

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

  • 導入手順はAndroid向けだけ:公式の導入ガイドはAndroid(com.google.firebase:firebase-pnv 17.0.0)のみで、iOSとWebの手順は公開されていません。
  • 対応キャリアに日本が無い:料金と対応キャリアのページ(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 です。料金は認証の成功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のメール列挙保護)、どちらの場合もサーバーは 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を、本登録時に同じアカウントへ引き継ぐ方法

会員登録の前にカートやお気に入りを使わせたい場合は、匿名認証で 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エミュレーターへの接続)。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組み込みで扱っています。

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

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

既存のモバイルアプリでは、2025年8月25日のFirebase Dynamic Links終了の影響も確認してください(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の終了と移行で扱っています。

会員基盤を既存システムから移す、社内IdPとのSSOやMFAを含めて設計する、といった案件では、認証方式の選定とユーザー移行の手順を、実装前に整理する必要があります。株式会社一創の認証基盤・ID管理システム開発では、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 を取り出します。

関連記事

お気に入りに入れた記事の一覧

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

この記事は以下の記事からリンクされています

ほか 6 件の記事からもリンクされています。

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.29 テックブログ ソニー生命の情報漏洩38.5万件|ログインできない理由、再開状況と窓口の電話番号
  2. 2026.10.31 テックブログ 松屋の不正アクセス:237万件の対象確認と窓口、漏れたパスワードの守り方
  3. 2026.11.01 コラム インボイスの経過措置とは?70%控除の期間と計算方法・帳簿の書き方・1億円制限【2026年10月改正対応】
  4. 2026.03.17 コラム 所得金額調整控除とは?年収850万円超で最大15万円、対象者と計算式・申告書の書き方【令和8年分】
  5. 2026.11.02 コラム 法定福利費とは?含まれる保険と会社負担率、令和8年度の料率と仕訳例【2026年版】

RELATED POSTS 関連記事

目次