0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.js × Firebase AuthenticationのsignInWithRedirect()特有のエラー : "Unable to process request due to missing initial state..." sessionStorageの消失

Last updated at Posted at 2025-10-07

エラー内容

Unable to process request due to missing initial state. This may happen if browser sessionStorage is inaccessible or accidentally cleared. Some specific scenarios are - 1) Using IDP-Initiated SAML SSO. 2) Using signInWithRedirect in a storage-partitioned browser environment.

概要

Firebase Authentication を用いてsignInWithRedirect()でGoogleアカウントでのログインを実装したところ発生。
なぜ起きるのか。
FirebaseのsignInWithRedirect()は次のように動く。

  1. ログインボタン押下 → Firebaseが一時的な状態をsessionStorageに保存する
  2. Googleのログイン画面(認証ページ)にリダイレクト
  3. ログイン成功後 → 元のアプリ(http://localhost:3000/)にリダイレクト
  4. FirebaseがsessionStorageからデータを読み取って「このリダイレクトはログイン処理の続きだ」と判断し、セッションを復元。getRedirectResult()で最終的にユーザ情報を取得。

つまり「この途中のsessionStorage」が消えるとFirebaseは「前の状態を再現できない!」となって上記エラーを出します。
途中状態の保持にブラウザのsessionStorageを使っているのが重要です。
signInWithRedirect()sessionStorageに一時データを書き込んでからGoogleに遷移しますが、戻ってきた時にsessionStorageが空だとまさにこのエラーになります。

これはFirebase側の不具合ではなく、ブラウザ仕様+リダイレクト認証の副作用です。
ブラウザはFirefoxを使用しています。
ChatGPTに上記の副作用の点を質問すると、

最近のブラウザ(特にFirefoxとSafari)では、プライバシー強化のため、サイト分離(Storage Partitioning)という仕様が導入されています。
これにより

  • サードパーティコンテキスト(=Googleログイン画面のような別ドメイン)から戻ってきた時、
  • 元のサイトのsessionStorage初期化される(=空になる)場合があります。

その結果Firebaseが状態を復元できず、最初に書いたエラーが発生する
とのことです。

このエラーが出る代表的な状況

原因 説明
Next.js(App Router) × サーバサイドで動くコード signInWithRedirect()はクライエント側でしか動きません。"use client"がついていないと失敗
ブラウザの設定(サードパーティCookie・partitioned storage) SafariやBraveのように、ストレージを分離しているブラウザで起こることがあります
Next.jsのHot Reload(開発中) 開発サーバーの再起動・リロードでsessionStorageが消えてエラーになることも
複数ドメイン(localhost → firebaseapp.com)間のリダイレクト ドメインが違うとsessionStorageが共有されず、初期状態を復元できない

解決策

参考(公式ドキュメント)

公式ドキュメントにsignInWithRedirect()を使用するベストプラクティスが載っていました。

  • Firebase Hostingを使用してfirebaseapp.comのサブドメインでアプリをホストする場合、この問題による影響はなく、特別な対応は必要ありません(実施したが解決せず)
  • Firebase Hostingを使用してカスタムドメインまたはweb.appのサブドメインでアプリをホストする場合は、「オプション1」を使用します。
  • Firebase以外のサービスでアプリをホストしている場合は、「オプション2」,「オプション3」,「オプション4」,「オプション5」 を使用します。

オプション1: カスタムドメインをauthDomainとして使用できるようにFirebase構成を更新する
オプション2: signInWithPopup()に切り替える
オプション3: firebaseapp.comへのプロキシ認証リクエスト
オプション4: ログインヘルパーコードを自社ドメイン内でホストする
オプション5: プロバイダのログインを独自に処理する

今回はカスタムドメインでなくFirebase Hostingを使用していますが、開発環境ではsignInWithPopup()を使うというオプション2を採用しました。
signInWithPopup()sessionStorageに依存せず、ページ遷移しないのでNext.js(App Router)とも非常に相性が良いです。

解決策

認証を実装しているAuthContext.jsで次のように、開発環境ではsignInWithPopup()を使用し、本番環境では異なるドメイン間ではないため、signInWithRedirect()を使用する方針を取りました。
useEffect()で認証状態を監視し、redirect方式で戻ってきた場合のエラー処理も実装しました。

/context/AuthContext.jsx
"use client";
import { createContext, useContext, useEffect, useState } from "react";
import {
  GoogleAuthProvider,
  signInWithPopup,
  signInWithRedirect,
  getRedirectResult,
  onAuthStateChanged,
  signOut,
} from "firebase/auth";
import { auth } from "@/firebase";

const AuthContext = createContext();

export const useAuth = () => useContext(AuthContext);

export default function AuthProvider({ children }) {
  const [currentUser, setCurrentUser] = useState(null);
  const [loading, setLoading] = useState(true);

  // 開発環境かどうかを判定
  const isLocalhost =
    typeof window !== "undefined" &&
    (window.location.hostname === "localhost" ||
      window.location.hostname === "127.0.0.1");

  const login = async () => {
    const provider = new GoogleAuthProvider();

    try {
      if (isLocalhost) {
        // ★ signInWithPopupで対応
        console.log("🔹 Using signInWithPopup (localhost dev mode)");
        await signInWithPopup(auth, provider);
      } else {
        console.log("🔹 Using signInWithRedirect (production)");
        await signInWithRedirect(auth, provider);
      }
    } catch (error) {
      console.error("Login error:", error);
    }
  };

  const logout = async () => {
    await signOut(auth);
  };

  useEffect(() => {
    // 1. 認証状態を監視
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      setCurrentUser(user);
      setLoading(false);
    });

    // 2. redirect方式で戻ってきた場合の処理
    getRedirectResult(auth).catch((err) => {
      if (err && err.code !== "auth/no-auth-event") {
        console.warn("getRedirectResult error:", err);
      }
    });

    return () => unsubscribe();
  }, []);

  const value = { currentUser, login, logout };

  return (
    <AuthContext.Provider value={value}>
      {loading ? <p>Loading...</p> : children}
    </AuthContext.Provider>
  );
}

呼び出し側のログインボタンを押下すると、無事ポップアップウインドウが表示されGoogleアカウントでログインができました!

付記

useAuth()で取得したcurrentUser,login,logoutをアプリ全体で使用できるようにするため、layout.jsで以下のようにAuthProviderで囲みました。

layout.jsx
import AuthProvider from "@/context/AuthContext";

export const metadata = {
  title: "TODOアプリ",
  description: "Firebase認証付きTODOアプリ",
};

export default function RootLayout({ children }) {
  return (
    <html lang="ja">
      <body>
        <AuthProvider>{children}</AuthProvider>
      </body>
    </html>
  );
}

呼び出し側は以下のようになっています。

/app/page.jsx
"use client";
import { useAuth } from "@/context/AuthContext";

export default function Index() {
  const { currentUser, login, logout } = useAuth();

  const handleLoginButton = () => {
    login();
  };

  const handleLogoutButton = () => {
    logout();
  };

  return (
    <>
      <div style={{ textAlign: "center" }}>
        <h1>TODOアプリケーション</h1>
        {currentUser && (
          <div>
            <h2>ログインしています</h2>
            <button onClick={handleLogoutButton}>ログアウト</button>
          </div>
        )}
        {!currentUser && (
          <div>
            <h2>ログインしていません</h2>
            <button onClick={handleLoginButton}>ログイン</button>
          </div>
        )}
      </div>
    </>
  );
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?