Skip to main content

第15章:セッション保持:Persistence(local/session/none)を選べるようにする💾

この章は「ログインが消える/残りすぎる」問題を、ちゃんとコントロールできるようになる回だよ🙂✨ “どの端末で、どれくらいログイン状態を残す?” を選べるようにして、認証の完成度をグッと上げよう💪


読む📚👀(まずはイメージ)

Persistence Types

Webアプリの認証は、基本的に「ブラウザを閉じてもログイン状態を残す(=便利)」がデフォルト。だけど、共有PCとかだと危ないよね😇 そこで Persistence(保持方式) を選べるようにする、って話! (Firebase)

保持方式は3つ👇 (Firebase)

  • local(LOCAL):ブラウザを閉じても残る💾(明示的にログアウトしない限り残る)
  • session(SESSION):そのタブ/ウィンドウを閉じたら消える🧼
  • none(NONE):メモリだけ(リロードで消える)🫥

そして重要ポイント: 何も指定しなければ、ブラウザでは default = local になるよ🧠 (Firebase)


手を動かす🛠️✨(やることはシンプル)

目標はこれ👇 ✅ ログイン画面に「保持のしかた」を付ける ✅ ログイン処理の直前で setPersistence() を呼ぶ ✅ 事故りやすい罠(タブ・初期化・redirect)を避ける


1) 保持方式の“選択肢”を作る🧩🎛️

まず、保持方式をまとめるユーティリティを用意しよう(TypeScript)👇

// authPersistence.ts
import type { Auth } from "firebase/auth";
import {
setPersistence,
browserLocalPersistence,
browserSessionPersistence,
inMemoryPersistence,
} from "firebase/auth";

export type PersistenceMode = "local" | "session" | "none";

export async function applyPersistence(auth: Auth, mode: PersistenceMode) {
switch (mode) {
case "session":
return setPersistence(auth, browserSessionPersistence);
case "none":
return setPersistence(auth, inMemoryPersistence);
case "local":
default:
return setPersistence(auth, browserLocalPersistence);
}
}

setPersistence() は「今のセッション」と「これからのサインイン」に効くし、ストレージ間のコピーが終わるまで Promise が解決しないよ🧠✨(だから await 推奨) (Firebase)


2) ログイン直前に applyPersistence() → ログイン実行🚪🔐

Implementation Flow

メールログイン版📧

import { signInWithEmailAndPassword } from "firebase/auth";
import { auth } from "./firebase"; // 既に作ってある想定
import { applyPersistence, type PersistenceMode } from "./authPersistence";

export async function loginWithEmail(
email: string,
password: string,
mode: PersistenceMode
) {
await applyPersistence(auth, mode);
return signInWithEmailAndPassword(auth, email, password);
}

Googleログイン(Popup)版🌈

import { GoogleAuthProvider, signInWithPopup } from "firebase/auth";
import { auth } from "./firebase";
import { applyPersistence, type PersistenceMode } from "./authPersistence";

export async function loginWithGooglePopup(mode: PersistenceMode) {
await applyPersistence(auth, mode);
const provider = new GoogleAuthProvider();
return signInWithPopup(auth, provider);
}

3) ログイン画面に「保持のしかた(Remember me)」を追加🖥️🧷

Login UI with Persistence

ログイン画面でこういうラジオを出すのが分かりやすい🙂✨ (選んだ値は localStorage に保存して、次回も同じ選択にする)

import { useEffect, useState } from "react";
import type { PersistenceMode } from "./authPersistence";
import { loginWithEmail, loginWithGooglePopup } from "./loginActions";

const KEY = "auth:persistence";

function loadMode(): PersistenceMode {
const v = localStorage.getItem(KEY);
if (v === "local" || v === "session" || v === "none") return v;
return "local";
}

export function LoginForm() {
const [mode, setMode] = useState<PersistenceMode>("local");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");

useEffect(() => {
setMode(loadMode());
}, []);

function updateMode(m: PersistenceMode) {
setMode(m);
localStorage.setItem(KEY, m);
}

return (
<div>
<h2>ログイン🔐</h2>

<fieldset>
<legend>ログイン状態の保持💾</legend>

<label>
<input
type="radio"
name="persist"
checked={mode === "local"}
onChange={() => updateMode("local")}
/>
このPCではログインを維持(おすすめ)
</label>

<label>
<input
type="radio"
name="persist"
checked={mode === "session"}
onChange={() => updateMode("session")}
/>
共有PCモード(閉じたら消える)
</label>

<label>
<input
type="radio"
name="persist"
checked={mode === "none"}
onChange={() => updateMode("none")}
/>
超安全モード(リロードで消える)
</label>
</fieldset>

<button onClick={() => loginWithEmail(email, password, mode)}>
メールでログイン📧
</button>

<button onClick={() => loginWithGooglePopup(mode)}>
Googleでログイン🌈
</button>
</div>
);
}

つまずきポイント集😵‍💫➡️🙂(ここ超大事)

A) “起動時に毎回 setPersistence(local)” はやりがち事故💥

Init Overwrite Trap

local がデフォルトだからといって、アプリ起動のたびに 明示的に setPersistence(browserLocalPersistence) を呼ぶと、状況によっては 既存の local セッションが消える報告があるよ😇 (GitHub)

おすすめ運用ルール👇

  • ログイン直前にだけ setPersistence()
  • ✅ 起動時は onAuthStateChanged で復元されるのを待つ(前章の背骨)
  • ⚠️ “毎回initで固定” は避ける(特に複数タブで痛い) (GitHub)

B) タブ挙動:local は“同期”、session/none は“別人”になれる🧠🪟

Tab Behavior

Firebase公式が期待挙動をはっきり書いてる👇 (Firebase)

  • session/none:タブごとに別ユーザーでログインできる(お互い見えない)
  • local:タブ間で同期される
  • local→session/none に切り替えると、他タブがログアウトされ得る

だから「共有PCモード(session)」は、“そのタブだけ”で完結するログインにしたい時に強い👍

C) Redirect を使う時の注意(上書き問題)🔁🧠

Redirect Persistence Risk

signInWithRedirect() は **“呼んだ時点の persistence を保持して、OAuth完了時に適用”**する挙動が基本。 でも、戻ってきたページで setPersistence() を呼ぶと、保持してた設定を上書きすることもあるよ⚠️ (Firebase)

👉 ルール:Redirect 使うなら **「どのページで persistence を決めるか」**を固定しよう🙂


ミニ課題🎯🧪

**「共有PCモード」**をちょい親切にしよう✨

  • mode === "session" のときだけ、ログイン画面に注意バナーを表示

    • 例:「このモードはウィンドウを閉じるとログインが消えるよ🧼」
  • さらに余裕あれば:ログイン後のマイページにも、現在のモードを表示🪪


チェック✅✅✅(自分で動作確認)

次のテストをやれば、Persistence の理解が一気に固まるよ💪

  • local:ログイン → ブラウザ終了 → 再起動 → まだログイン中✅
  • session:ログイン → タブ閉じる → 開き直し → ログアウト状態✅
  • none:ログイン → F5(リロード) → ログアウト状態✅
  • 追加:local でログイン中に別タブを開いた時、状態が同期される✅ (Firebase)

AIでUX強化🤖💬(迷った人を助けるボタン)

AI Persistence Advisor

ここで Firebase の AI Logic(Gemini) を混ぜると、体験がやさしくなる☺️✨ 例:「どれ選べばいい?」ボタンを押すと、状況に合わせて提案してくれる👍 (Firebase)

// aiSuggest.ts(イメージ)
import { getAI, getGenerativeModel, GoogleAIBackend } from "firebase/ai";
import { app } from "./firebaseApp";

const ai = getAI(app, { backend: new GoogleAIBackend() });
const model = getGenerativeModel(ai, { model: "gemini-2.5-flash" });

export async function suggestPersistence(sharedPc: boolean) {
const prompt = `
あなたはWebアプリの先生です。
ログイン状態の保持方式を、初心者にもわかる言葉でおすすめして。
前提:sharedPc=${sharedPc}
候補:local/session/none
最後に「おすすめ:○○」を1行で出して。
`.trim();

const res = await model.generateContent(prompt);
return res.response.text();
}

開発AIの使いどころ🧠⚡(この章と相性よすぎ)

  • Antigravity:ログイン画面に「保持のしかた」を足すUIを丸ごと作らせる(Mission形式で)🚀 (Google Codelabs)
  • Gemini CLI:リポジトリ全体を見て、「setPersistence を init で呼んでない?」「redirect戻りで上書きしてない?」をレビューさせる🔎 (CLIはMCPやツール連携も想定されてるよ) (Google Cloud Documentation)

ここまでできたら、ログインが“勝手に消える/残りすぎる”問題がほぼ消えるはず🙂💪 次の章(ルートガード)に行くと、さらに「アプリとして完成」って感じになるよ🚧✨