🔐 Next.jsでログイン・ログアウト機能を実装しよう!初心者向けに徹底解説(2025年版)

Next.jsでアプリケーションを作っていると、必ずと言っていいほど必要になる「ログイン・ログアウト機能」。本記事では、認証の仕組みから、セッション管理の方法実装コード例まで、初心者の方にもわかりやすく丁寧に解説します!


✅ この記事でわかること

  • 認証とセッションの基礎
  • 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 Routeriron-sessionを使うことで、簡潔かつセキュアなログイン・ログアウト機能を構築できます。初心者の方でも、今回の手順をそのまま使えば十分に実装可能です。

  • 🔑 iron-sessionでセッション管理
  • 🔐 bcryptで安全なパスワード保存
  • 🚪 ログイン・ログアウトAPIで認証制御
  • 🔍 APIを叩いてセッション判定

✨ 今後のステップ

  • Firebase Authenticationの導入(Googleログインなど)
  • JWT方式への移行
  • パスワードリセット機能の追加
おすすめの記事