
Next.jsでアプリケーションを作っていると、必ずと言っていいほど必要になる「ログイン・ログアウト機能」。本記事では、認証の仕組みから、セッション管理の方法、実装コード例まで、初心者の方にもわかりやすく丁寧に解説します!
Contents
- 1 ✅ この記事でわかること
- 2 🧠 認証とは?セッションとは?
- 3 🛠 使用する技術
- 4 📁 プロジェクト構成例(簡易)
- 5 🔐 iron-sessionのセットアップ
- 6 🔐 ログインAPI /app/api/login/route.ts
- 7 🔓 ログアウトAPI /app/api/logout/route.ts
- 8 📌 セッション状態の取得 /app/api/session/route.ts
- 9 🧑💻 ログインフォーム /app/login/page.tsx
- 10 🚪 ログアウトページ /app/logout/page.tsx
- 11 ✅ 認証が必要なページの保護
- 12 🐞 よくあるエラーと対処法
- 13 🎯 まとめ
- 14 ✨ 今後のステップ
✅ この記事でわかること
- 認証とセッションの基礎
- Next.jsでのログイン・ログアウト実装の流れ
bcryptでのパスワード管理iron-sessionでセッションを保持する方法- 実際のコード例(APIルート、ログインUIなど)
🧠 認証とは?セッションとは?
まず用語の整理をしておきましょう。
🔑 認証(Authentication)
ユーザーが「誰であるか」を確認する行為です。例えば:
- メールアドレスとパスワードの入力
- GoogleやTwitterでログインする(OAuth)
🧾 セッション(Session)
一度認証されたユーザーを、その後のアクセスでも「認証済み」として扱う仕組み。通常、セッションIDをCookieに保存し、リクエストごとに確認します。
🛠 使用する技術
今回使う技術やパッケージは以下です:
| 項目 | 内容 |
|---|---|
| フレームワーク | Next.js App Router |
| セッション管理 | iron-session |
| パスワードハッシュ化 | bcryptjs |
| データベース | Firebase Firestore もしくはJSONファイルなど |
📁 プロジェクト構成例(簡易)
/app
├── login/page.tsx
├── logout/page.tsx
└── account/page.tsx
/app/api
├── login/route.ts
├── logout/route.ts
└── session/route.ts
/lib
└── sessionOptions.ts
🔐 iron-sessionのセットアップ
npm install iron-session
/lib/sessionOptions.ts
import { IronSessionOptions } from 'iron-session';
export const sessionOptions: IronSessionOptions = {
password: process.env.SESSION_SECRET || '超強力な秘密鍵をここに',
cookieName: 'procom_session',
cookieOptions: {
secure: process.env.NODE_ENV === 'production',
},
};
declare module 'iron-session' {
interface IronSessionData {
uid?: string;
email?: string;
}
}
🔐 ログインAPI /app/api/login/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { getIronSession } from 'iron-session';
import { sessionOptions } from '@/lib/sessionOptions';
import bcrypt from 'bcryptjs';
import { getUserByEmail } from '@/lib/getUser'; // FirestoreやDBから取得する関数
export async function POST(req: NextRequest) {
const { email, password } = await req.json();
const user = await getUserByEmail(email);
if (!user) {
return NextResponse.json({ error: 'ユーザーが見つかりません' }, { status: 404 });
}
const passwordMatch = await bcrypt.compare(password, user.hashedPassword);
if (!passwordMatch) {
return NextResponse.json({ error: 'パスワードが違います' }, { status: 401 });
}
const res = NextResponse.json({ ok: true });
const session = await getIronSession(req, res, sessionOptions);
session.uid = user.uid;
session.email = user.email;
await session.save();
return res;
}
🔓 ログアウトAPI /app/api/logout/route.ts
import { getIronSession } from 'iron-session';
import { sessionOptions } from '@/lib/sessionOptions';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(req: NextRequest) {
const res = NextResponse.json({ ok: true });
const session = await getIronSession(req, res, sessionOptions);
await session.destroy();
return res;
}
📌 セッション状態の取得 /app/api/session/route.ts
import { getIronSession } from 'iron-session';
import { sessionOptions } from '@/lib/sessionOptions';
import { NextRequest, NextResponse } from 'next/server';
export async function GET(req: NextRequest) {
const session = await getIronSession(req, NextResponse.next(), sessionOptions);
if (session?.uid) {
return NextResponse.json({ loggedIn: true, uid: session.uid, email: session.email });
} else {
return NextResponse.json({ loggedIn: false });
}
}
🧑💻 ログインフォーム /app/login/page.tsx
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export default function LoginPage() {
const router = useRouter();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
async function handleLogin(e: React.FormEvent) {
e.preventDefault();
const res = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
headers: { 'Content-Type': 'application/json' },
});
if (res.ok) {
router.push('/account');
} else {
alert('ログイン失敗');
}
}
return (
<form onSubmit={handleLogin}>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} required placeholder="メールアドレス" />
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} required placeholder="パスワード" />
<button type="submit">ログイン</button>
</form>
);
}
🚪 ログアウトページ /app/logout/page.tsx
'use client';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
export default function LogoutPage() {
const router = useRouter();
useEffect(() => {
fetch('/api/logout', { method: 'POST' }).then(() => {
router.push('/login');
});
}, []);
return <p>ログアウト中です...</p>;
}
✅ 認証が必要なページの保護
ページロード時に /api/session を叩いて、未ログインならリダイレクトさせる処理を使いましょう。
'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
export default function AccountPage() {
const router = useRouter();
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/session')
.then(res => res.json())
.then(data => {
if (!data.loggedIn) {
router.push('/login');
} else {
setLoading(false);
}
});
}, []);
if (loading) return <p>確認中...</p>;
return <div>ようこそ、マイページへ!</div>;
}
🐞 よくあるエラーと対処法
| エラー | 原因 | 解決策 |
|---|---|---|
500 Internal Server Error | セッションが不正 | 環境変数 SESSION_SECRET を強力な文字列にする |
401 Unauthorized | パスワードが一致しない | bcrypt.compare で検証 |
| セッションが保存されない | CookieがSecure | ローカル開発時は secure: false にする |
🎯 まとめ
Next.jsでは、App Routerとiron-sessionを使うことで、簡潔かつセキュアなログイン・ログアウト機能を構築できます。初心者の方でも、今回の手順をそのまま使えば十分に実装可能です。
- 🔑 iron-sessionでセッション管理
- 🔐 bcryptで安全なパスワード保存
- 🚪 ログイン・ログアウトAPIで認証制御
- 🔍 APIを叩いてセッション判定
✨ 今後のステップ
- Firebase Authenticationの導入(Googleログインなど)
- JWT方式への移行
- パスワードリセット機能の追加








