Next.js(App Router + Route Handler)でのNextAuthを使った認証機能の実装
ご覧いただきありがとうございます。Furuyaです。
今回は、Next.js(App Router + Route Handler)でのNextAuthを使った認証機能の実装についてまとめます。
NextAuthを使用することで、様々な認証プロバイダー(GitHub、Google、Cognitoなど)との連携が容易になります。
環境
- MacBook Pro 13-inch, 2020, Four Thunderbolt 3 ports
- macOS:macOS Sequoia 15.4
- Shell: zsh
トークンの管理方式について
セッション管理の方法にはいくつかの選択肢があります。
代表的なものは以下の3つです。
- Cookie に保存する方式
- LocalStorage に保存する方式
- KVS(Redis など)に保存してサーバーサイドで管理する方式
それぞれにメリット・デメリットがありますが、今回は NextAuth の標準的な仕組みに従い、Cookieベースのセッション管理(JWT戦略) を採用しています。
NextAuthはこの方式を前提とした設計であり、特にサーバーレスやAPIベースの構成において扱いやすく、セキュリティ面でも一定のベストプラクティスが組み込まれています。
NextAuth におけるトークン処理の流れ
1. プロバイダーとの連携
認証処理は OIDC / OAuth 2.0 に基づき、外部プロバイダー(例:Google、GitHub、Cognito など)とやり取りします。
このとき、アプリケーションは client_id
と client_secret
を用いてプロバイダーと通信し、認可コードを受け取った後、アクセストークン(JWTなど)を取得します。
このアクセストークンには以下のような情報が含まれます。
- プロバイダーによって認証されたユーザー情報
- スコープ(アクセス可能なリソースの範囲)
- 有効期限
2. NextAuth による独自 JWT の生成
取得した情報を元に、NextAuth は独自の JWT を生成します。
- 必要に応じて
callbacks.jwt()
でアプリケーション独自の情報(ユーザーID、ロール、組織など)を追加できます - 生成された JWT は、署名された Cookie(HTTPOnly)としてクライアントに保存され、以後のリクエストで自動的に送信されます
この2段階の処理により、
- 認証プロバイダーに依存しない共通トークン形式を維持できる
- アプリケーションに必要な情報をトークンに統合できる
- サーバー側でステートレスにセッションを検証・運用できるようになります
何が漏れると不正アクセスされるのか?
NextAuth における「ログイン済みかどうか」の判断材料は、NextAuth が生成し、ブラウザの Cookie に保存している JWT です。
この JWT が第三者に取得されると、トークンの有効期限内であれば、そのユーザーとして任意の操作が可能になってしまいます。
また、client_secret
が漏洩すると、悪意のあるユーザーが正規のアプリケーションになりすまし、プロバイダーとの認証を不正に成立させる可能性があります。
特に守るべき情報
- JWT(NextAuth が生成したセッション用トークン)
- プロバイダー連携時に使用する
client_id
/client_secret
- NextAuth の
NEXTAUTH_SECRET
(トークンの署名や暗号化に使用)
セキュリティ対策とその効果(障壁)
NextAuth では以下のセキュリティ対策が組み込まれており、実際に攻撃者がセッションを奪取するための障壁として機能します:
NEXTAUTH_SECRET
)
JWTの暗号化・署名(- JWT は署名(および設定に応じて暗号化)されており、トークンの改ざんを防止します。
-
NEXTAUTH_SECRET
が漏れない限り、トークンの中身を復号したり、不正に生成することは実質不可能です。
攻撃者が仮にトークンを入手しても、中身を安全にカスタマイズ・再利用することは困難です。
HttpOnly
, Secure
, SameSite
属性を設定
Cookieに -
HttpOnly
: JavaScript からのアクセスを遮断(XSS 対策) -
Secure
: HTTPS 接続時のみ送信(盗聴対策) -
SameSite=Lax
orStrict
: 外部サイトからの不正リクエストを遮断(CSRF 対策)
これにより、トークンの窃取経路(XSS/CSRF)を大幅に制限できます。
callbacks.jwt()
)
セッションの有効期限・再生成(- セッションには明確な有効期限を設けており、定期的なトークン更新が可能です。
- ユーザー属性や権限の変更があっても、トークン再生成の中で反映できます。
仮にトークンが漏洩しても、期限切れ後は無効化され、恒久的な被害を防止できます。
このように、NextAuth のトークン管理は、漏れてはいけない情報は何であり、漏れた場合にどう守るかを意識した設計になっています。
次の章では、実際の実装コードや設定ファイルの構成について解説していきます。
ディレクトリ構成
プロジェクトのディレクトリ構成は以下のようになります:
my-nextjs-app/
├── app/
│ ├── api/
│ │ ├── auth/
│ │ │ └── [...nextauth]/
│ │ │ └── route.ts
│ │ └── example/
│ │ └── route.ts
│ ├── ssr/
│ │ └── page.tsx
│ ├── csr/
│ │ └── page.tsx
│ └── page.tsx
├── lib/
│ └── auth/
│ ├── config.ts
│ └── session.ts
├── middleware.ts
├── ...
NextAuthの設定
1. 認証設定ファイルの作成
まず、NextAuthの設定ファイルを作成します。このファイルでは、認証プロバイダーの設定やセッション管理の設定を行います。
import { type NextAuthConfig } from 'next-auth';
export const authConfig: NextAuthConfig = {
providers: [
// プロバイダーは任意(例: GitHub, Google, Cognito etc.)
],
session: {
strategy: 'jwt',
},
callbacks: {
async jwt({ token, account, user }) {
// 必要に応じてトークンに情報を追加
return token;
},
async session({ session, token }) {
// セッションにカスタム情報を追加可能
return session;
},
},
};
2. APIルートの設定
NextAuthのAPIルートを設定します。このファイルは認証関連のエンドポイントを提供します。
import NextAuth from 'next-auth';
import { authConfig } from '@/lib/auth/config';
const handler = NextAuth(authConfig);
export { handler as GET, handler as POST };
3. セッション管理のユーティリティ
サーバーサイドでセッションを取得するためのユーティリティ関数を作成します。
import { getServerSession } from 'next-auth';
import { authConfig as authOptions } from '@/lib/auth/config';
export const getAuthSession = () => getServerSession(authOptions);
認証機能の利用例
1. APIルートでの認証チェック
APIルートで認証状態を確認する方法です。
getToken
関数を使用してリクエストからトークンを取得し、認証状態を確認します。
import { NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';
export async function GET(req: Request) {
// getToken関数でリクエストからトークンを取得
const token = await getToken({ req });
// トークンが存在しない場合は401エラーを返す
if (!token) {
return new NextResponse('Unauthorized', { status: 401 });
}
// その後の処理…
}
解説
基本機能
-
getToken
関数はリクエストからJWTトークンを取得します
動作の仕組み
-
authConfig
のsession.strategy: 'jwt'
設定に基づいて動作 - リクエストのCookieからJWTトークンを取得
- トークンの有効性を検証し、デコードされたペイロードを返却
- トークンが無効な場合は
null
を返却
callbacksとの関係
- 通常の getToken 実行時には callbacks.jwt() は実行されませんが、decode/encode をカスタマイズしている場合は一部処理が関与することがあります
- トークンの検証のみが行われ、デコードされたペイロードが返却されます
- トークンの更新や追加情報の付与は行われません
2. サーバーサイドレンダリングでの認証
サーバーサイドで認証状態を確認し、ユーザー情報を表示する方法です。
getAuthSession
関数を使用してセッション情報を取得します。
import { redirect } from 'next/navigation';
import { getAuthSession } from '@/lib/auth/session';
export default async function SSRPage() {
// getAuthSession関数でセッション情報を取得
const session = await getAuthSession();
return (
<div>
<h1>ダッシュボード</h1>
<p>ようこそ、{session.user?.name} さん</p>
</div>
);
}
解説
基本機能
-
getAuthSession
関数はサーバーサイドでのみ使用可能です - セッション情報からユーザー名を取得し、表示します
動作の仕組み
-
authConfig
のsession.strategy
に基づいて動作 - サーバーサイドでCookieからセッション情報を取得
-
callbacks.session
で定義された処理を実行し、カスタマイズされたセッション情報を返却 - セッションが無効な場合は
null
を返却
callbacksとの関係
-
getAuthSession
実行時にcallbacks.session()
が実行されます -
callbacks.session()
はcallbacks.jwt()
で生成されたトークンの情報を基にセッション情報を構築します - セッション情報に追加のカスタマイズが必要な場合は、
callbacks.session()
内で実装します
3. クライアントサイドでの認証
クライアントサイドで認証状態を管理し、ユーザー情報を表示する方法です。
useSession
フックを使用して認証状態を監視します。
'use client';
import { useSession } from 'next-auth/react';
export default function CSRPage() {
// useSessionフックで認証状態を取得
const { data: session, status } = useSession();
// ローディング中の表示
if (status === 'loading') {
return <p>読み込み中...</p>;
}
// 未認証状態の表示
if (!session) {
return <p>ログインしていません</p>;
}
return (
<div>
<p>こんにちは、{session.user?.name} さん!</p>
<p>Email: {session.user?.email}</p>
</div>
);
}
解説
基本機能
-
'use client'
ディレクティブでクライアントコンポーネントとして指定 -
useSession
フックは3つの状態を管理:-
loading
: 認証状態の読み込み中 -
authenticated
: 認証済み -
unauthenticated
: 未認証
-
動作の仕組み
-
authConfig
の設定に基づいて動作 - クライアントサイドでセッション状態をリアルタイムに監視
-
callbacks.session
で定義された処理を実行し、カスタマイズされたセッション情報を取得 - セッションの変更を検知し、自動的にコンポーネントを再レンダリング
callbacksとの関係
-
useSession
は内部的にgetSession
を使用し、callbacks.session()
の結果を取得します - セッションの更新時(例:トークンのリフレッシュ)には
callbacks.jwt()
が実行されます - セッション情報の変更を検知すると、自動的に
callbacks.session()
が再実行されます - クライアントサイドでのセッション管理は、サーバーサイドの
callbacks
の結果に依存します
以上で「Next.js(App Router + Route Handler)でのNextAuthを使った認証機能の実装」は完了となります!
Discussion