- Auth.js v5 は単一の
NextAuth() 呼び出しで全機能をエクスポートし、v4 の authOptions や getServerSession は不要です。
- Next.js 16 では
middleware.ts が proxy.ts にリネームされたため、保護ルートの実装ファイル名を変更する必要があります。
AUTH_GITHUB_ID など AUTH_ プレフィックスの環境変数は自動的にプロバイダー設定にマッピングされ、コード側の記述が不要になります。
- セッション戦略は JWT(エッジランタイム・サーバーレス向け)とデータベース(細かい無効化が必要な場合)の2択で、用途で使い分けます。
- マジックリンク認証は Resend・Nodemailer などのメールプロバイダーと組み合わせ、パスワード不要の安全なログインを実現できます。
- パスキー(WebAuthn)が v5 で正式サポートされ、SMS・TOTP より高い耐フィッシング性を持つ二要素を実装可能です。
Auth.js v5 とは何か:v4 からの主な変更点
Auth.js v5 は NextAuth.js を完全に書き直した認証ライブラリで、Next.js だけでなく SvelteKit・Express・Qwik など複数フレームワークに対応するため @auth/core に共通基盤が抽出されました。Next.js 用の薄いラッパーは next-auth パッケージとして引き続き配布されていて、v4 を使ってきた開発者にとっては「設定ファイルの形が変わる」点が最大の差分になります。
v4 では authOptions オブジェクトを定義し、各 API ルートで getServerSession(authOptions) を呼び出してセッションを取得していました。v5 では NextAuth() を1度だけ呼び出し、その戻り値から handlers・auth・signIn・signOut をエクスポートします。Server Components からは単に const session = await auth() と書くだけでセッションを取得でき、実際に移行してみるとコード量は v4 比で約 30% 減るケースが多いです(私の前回のプロジェクトでも、API ルート12個分のボイラープレートが消えました)。
仕様面の主な変更点は次の通りです。
- OAuth/OIDC 仕様の厳格化:
@auth/core 化に伴い OAuth 2.1 と OIDC への準拠が強化され、独自実装プロバイダーは挙動を見直す必要があります。
- OAuth 1.0 サポート廃止:Twitter v1 など OAuth 1.0 を使う旧プロバイダーは利用不可になりました(Twitter は OAuth 2.0 へ移行済み)。
- 環境変数の自動推論:
AUTH_GITHUB_ID / AUTH_GITHUB_SECRET のように AUTH_{PROVIDER}_{ID|SECRET} 形式で命名すれば、プロバイダー設定で clientId を明示しなくても自動で読み込まれます。
- パスキー(WebAuthn)対応:
@auth/core/providers/passkey が追加され、生体認証・セキュリティキーを使ったログインがネイティブで可能になりました。
- 最低 Next.js バージョン 14.0:App Router 前提の API になり、Pages Router 互換コードが整理されました。
移行作業の詳細は Auth.js 公式の v5 移行ガイド にステップ別の差分が掲載されています。v4 から既存プロジェクトを移行する場合は、まず auth.config.ts に共通設定を切り出して、エッジ互換のミドルウェアと Node.js ランタイムの API ルートで設定を共有する形に再構成するのが定石ですね。
インストールと初期セットアップ
Next.js 16 プロジェクトに Auth.js v5 を導入する手順は3ステップです。まずパッケージをインストールします。安定版がリリース済みであれば next-auth、ベータ追従であれば @beta タグを指定します。
npm install next-auth@beta
# または
pnpm add next-auth@beta
# Prisma を使う場合
pnpm add @auth/prisma-adapter @prisma/client
次に、プロジェクトルート(または src/)に auth.ts を作成します。ここが、すべてのエクスポート元になります。
// auth.ts
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
import Google from "next-auth/providers/google"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [GitHub, Google],
session: { strategy: "jwt" },
pages: {
signIn: "/login",
},
})
最後に、Auth.js が OAuth コールバックを受ける Route Handler を app/api/auth/[...nextauth]/route.ts に配置します。
// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth"
export const { GET, POST } = handlers
環境変数は .env.local に最低でも次の3つを設定します。AUTH_SECRET は JWT 署名と CSRF トークン生成に使われるため、本番環境では必ず 32 バイト以上のランダム値にしてください。openssl rand -base64 32 または npx auth secret コマンドで生成できます。
AUTH_SECRET=your-32-byte-random-string
AUTH_GITHUB_ID=Iv1.xxxxxxxxxxxx
AUTH_GITHUB_SECRET=xxxxxxxxxxxxxxxx
AUTH_GOOGLE_ID=xxxxxxxxxx.apps.googleusercontent.com
AUTH_GOOGLE_SECRET=xxxxxxxxxxxxxxxx
OAuth プロバイダー実装(GitHub / Google)
OAuth ログインは、Auth.js v5 で最も実装が簡単な認証方式です。AUTH_ プレフィックスの環境変数を設定するだけで、コード側は providers 配列にプロバイダー名を追加するだけで動作します。正直、ここは v4 と比べると衝撃的なくらい記述量が減りました。
// auth.ts に追記
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
import Google from "next-auth/providers/google"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
GitHub, // AUTH_GITHUB_ID, AUTH_GITHUB_SECRET を自動読込
Google({
authorization: {
params: {
prompt: "consent",
access_type: "offline",
response_type: "code",
},
},
}),
],
callbacks: {
async signIn({ user, account, profile }) {
// 特定ドメインのみ許可するなどのカスタムロジック
if (account?.provider === "google") {
return profile?.email_verified === true
}
return true
},
async jwt({ token, user, account }) {
if (account) {
token.provider = account.provider
token.accessToken = account.access_token
}
return token
},
async session({ session, token }) {
if (token) {
session.user.provider = token.provider as string
}
return session
},
},
})
クライアントコンポーネントからログインボタンを設置するには、signIn を Server Action として呼び出すのが v5 の推奨パターンです。クライアント側で "next-auth/react" をインポートする必要はありません。
// app/login/page.tsx
import { signIn } from "@/auth"
export default function LoginPage() {
return (
<div className="login-buttons">
<form action={async () => {
"use server"
await signIn("github", { redirectTo: "/dashboard" })
}}>
<button type="submit">GitHub でログイン</button>
</form>
<form action={async () => {
"use server"
await signIn("google", { redirectTo: "/dashboard" })
}}>
<button type="submit">Google でログイン</button>
</form>
</div>
)
}
GitHub OAuth App の設定では Authorization callback URL に https://your-domain.com/api/auth/callback/github を登録します。Google Cloud Console の場合は https://your-domain.com/api/auth/callback/google ですね。ローカル開発時は http://localhost:3000/api/auth/callback/{provider} も追加しておきましょう(ここを忘れて30分くらい悩んだことが、過去に何度かあります)。
OAuth コールバック後にユーザー情報をデータベースへ永続化したい場合は、後述の Database セッション戦略とアダプターを組み合わせます。永続化が不要なシンプルなアプリなら、JWT のみで完結します。Server Actions を活用したセキュアなフォーム処理については、Server Actions のセキュリティ実装ガイド も併せて参照してください。
メールマジックリンク認証の実装
マジックリンク認証は、ユーザーがメールアドレスを入力するとワンタイムリンクが届き、それをクリックするだけでログインできる方式です。パスワード不要のためフィッシング耐性が高く、SaaS や B2B プロダクトで採用が増えています。Auth.js v5 では next-auth/providers/resend または next-auth/providers/nodemailer を使って実装します。
Resend を使う場合の最小構成です。Resend は無料枠でも月 3,000 通まで送信でき、ドメイン認証も DNS レコード追加だけで完結するため、開発者体験が良好です。
// auth.ts
import NextAuth from "next-auth"
import Resend from "next-auth/providers/resend"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "@/lib/prisma"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma), // マジックリンクには DB が必須
providers: [
Resend({
from: "[email protected]",
// AUTH_RESEND_KEY を自動読込
}),
],
session: { strategy: "database" },
pages: {
signIn: "/login",
verifyRequest: "/login/check-email", // 「メールを確認してください」画面
},
})
マジックリンクは verification token を Account/VerificationToken テーブルに保存する必要があるため、必ずデータベースアダプター(Prisma・Drizzle・Supabase など)を併用します。Prisma の場合のスキーマは npx auth schema prisma コマンドで生成できます。
カスタムメールテンプレートを使いたい場合は、Resend プロバイダーに sendVerificationRequest を渡してテンプレートを上書きします。これによりブランドロゴや多言語対応の HTML メールを送信できます。
Credentials Provider でメール・パスワード認証
従来型のメール・パスワード認証は、Auth.js v5 でも Credentials プロバイダーで実装できますが、公式は OAuth・マジックリンク・パスキーの利用を推奨しています。これは Credentials Provider が CSRF 対策や DB 連携を開発者責任にゆだねるためです。それでも要件上必要な場合の実装例を示します。
// auth.ts
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
import bcrypt from "bcryptjs"
import { z } from "zod"
import { prisma } from "@/lib/prisma"
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8).max(128),
})
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Credentials({
credentials: {
email: { label: "メールアドレス", type: "email" },
password: { label: "パスワード", type: "password" },
},
async authorize(credentials) {
const parsed = loginSchema.safeParse(credentials)
if (!parsed.success) return null
const user = await prisma.user.findUnique({
where: { email: parsed.data.email },
})
if (!user?.passwordHash) return null
const ok = await bcrypt.compare(
parsed.data.password,
user.passwordHash
)
if (!ok) return null
return { id: user.id, email: user.email, name: user.name }
},
}),
],
session: { strategy: "jwt" },
})
パスワードハッシュは bcrypt(ラウンド 12 以上)または argon2id を使い、平文での保存は絶対に避けてください。また、ブルートフォース対策として Next.js Middleware を使ったレート制限 を proxy.ts 層で併用するのが定石です。
Next.js 16 の proxy.ts でルートを保護する
Next.js 16 の最も大きな破壊的変更のひとつが、middleware.ts から proxy.ts へのリネームです。役割は変わらず「リクエストがルートハンドラーやページに到達する前に介入する層」ですが、命名が実態(プロキシ的な前段処理)に即した形に整理されました。Next.js 15 以下から移行する場合はファイル名を変更するだけで動きます。
// proxy.ts (Next.js 16)
// 旧 middleware.ts の置き換え
export { auth as middleware } from "@/auth"
export const config = {
matcher: [
// /dashboard 配下と /api 配下を保護
"/dashboard/:path*",
"/api/protected/:path*",
// 静的アセットと _next を除外
"/((?!_next/static|_next/image|favicon.ico).*)",
],
}
細かいリダイレクトロジックを書きたい場合は、auth() を高階関数として使うパターンが便利です。例えば「未ログインユーザーは /login にリダイレクト、ログイン済みでロールが admin でなければ 403」といった複雑な制御を、1ファイルで実現できます。
// proxy.ts
import { auth } from "@/auth"
import { NextResponse } from "next/server"
export default auth((req) => {
const { nextUrl } = req
const isLoggedIn = !!req.auth
const isAdminRoute = nextUrl.pathname.startsWith("/admin")
const isLoginPage = nextUrl.pathname === "/login"
// ログイン済みユーザーが /login にアクセスした場合
if (isLoggedIn && isLoginPage) {
return NextResponse.redirect(new URL("/dashboard", nextUrl))
}
// /admin は admin ロールのみ
if (isAdminRoute && req.auth?.user?.role !== "admin") {
return NextResponse.redirect(new URL("/403", nextUrl))
}
// 未ログインで保護ルートにアクセス
if (!isLoggedIn && nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", nextUrl))
}
})
export const config = {
matcher: ["/((?!api/auth|_next|favicon.ico).*)"],
}
JWT と Database セッションどちらを選ぶか
Auth.js v5 のセッション戦略は session.strategy オプションで "jwt" または "database" を選択します。どちらが正解かはアプリの要件次第ですが、判断基準は明確です。比較表で整理します。
| 項目 | JWT セッション | Database セッション |
| セッション保存先 | 暗号化 Cookie | DB の Session テーブル |
| DB アダプター | 不要 | 必須(Prisma/Drizzle 等) |
| エッジランタイム互換 | ○ 完全対応 | △ DB が edge 対応必須 |
| セッション即時無効化 | × 期限切れまで有効 | ○ レコード削除で即時 |
| マジックリンク対応 | × 不可 | ○ 必須 |
| パフォーマンス | ○ DB 不要で高速 | △ リクエスト毎に DB 参照 |
| スケール時のコスト | 低 | 中〜高 |
| 推奨ユースケース | SPA・サーバーレス・OAuth のみ | SaaS・即時ログアウト要件 |
実務での選び方は次の通りです。OAuth のみで「ログアウトはセッション期限切れまで待ってよい」要件なら JWT、マジックリンクを使う場合や「管理画面から強制ログアウトを発動したい」要件があれば Database を選びます。両方の良いとこ取りとして、セッションは JWT で持ちつつユーザー情報は DB に保存するハイブリッド構成もよく使われます。Auth.js v5 では adapter を指定しても session.strategy: "jwt" にすればこの構成が可能です。個人的には、まずはこのハイブリッド構成から始めるのがおすすめですね。
Server Components と Server Actions での auth() 活用
v5 で最も体験が向上したのが、Server Components 内での認証状態取得です。v4 では getServerSession(authOptions) を毎回呼び出す必要がありましたが、v5 では await auth() だけで完結します。
// app/dashboard/page.tsx
import { auth } from "@/auth"
import { redirect } from "next/navigation"
export default async function DashboardPage() {
const session = await auth()
if (!session?.user) redirect("/login")
return (
<main>
<h1>ようこそ {session.user.name} さん</h1>
<p>メール: {session.user.email}</p>
</main>
)
}
Server Actions の中でも、同じ API でセッションを取得できます。これにより「ログインユーザーのみが投稿を作成できる」といった認可ロジックを Action 本体に組み込めます。
// app/posts/actions.ts
"use server"
import { auth } from "@/auth"
import { prisma } from "@/lib/prisma"
import { revalidatePath } from "next/cache"
export async function createPost(formData: FormData) {
const session = await auth()
if (!session?.user?.id) {
throw new Error("認証が必要です")
}
const title = String(formData.get("title") ?? "")
if (!title.trim()) throw new Error("タイトルは必須です")
await prisma.post.create({
data: { title, authorId: session.user.id },
})
revalidatePath("/posts")
}
Server Components で取得したセッション情報は React のキャッシュ層で同一リクエスト中に重複呼び出しが排除されるため、複数の子コンポーネントから auth() を呼んでも DB アクセスは発生しません。Next.js 16 の新しいキャッシュモデルとの相性については、Cache Components と use cache の解説 も参考になりますよ。
パスキー(WebAuthn)対応で耐フィッシング認証へ
Auth.js v5 では、パスキー(WebAuthn)が公式プロバイダーとして追加されました。パスキーはデバイスに紐づく秘密鍵で署名する公開鍵暗号方式の認証で、パスワードや SMS OTP より圧倒的にフィッシング耐性が高いのが特徴です。Apple・Google・Microsoft が iCloud Keychain・Google Password Manager・Windows Hello で同期に対応しており、ユーザー体験も大きく改善されました。
// auth.ts
import NextAuth from "next-auth"
import Passkey from "next-auth/providers/passkey"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "@/lib/prisma"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [Passkey],
experimental: { enableWebAuthn: true },
session: { strategy: "database" },
})
パスキーはチャレンジ・レスポンスを DB に保存するため Database セッションが必須です。また Prisma スキーマには Authenticator モデルの追加が必要なので、npx auth schema prisma を再実行してマイグレーションを当て直してください。クライアント側は通常の signIn("passkey") 呼び出しでブラウザの WebAuthn API が起動し、Face ID・Touch ID・YubiKey などで認証されます。
本番投入前のセキュリティチェックリスト
Auth.js は CSRF 対策・JWT 署名・PKCE などのセキュリティ機構をデフォルトで備えていますが、設定ミスで無効化されるケースがあるため、本番投入前に次の項目を確認してください。
- AUTH_SECRET が 32 バイト以上のランダム値で、Git にコミットされていない(
.env.local は .gitignore に必ず追加)。
- 本番ドメインで HTTPS が有効(Cookie の
Secure フラグが効くため)。
- OAuth コールバック URL が 本番ドメインのみ 登録されている(テスト用 URL を残さない)。
pages.error や pages.signIn で カスタムエラーページ を用意し、内部スタックを露出しない。
- Credentials Provider を使う場合は レート制限(IP/メール単位)を
proxy.ts に実装。
- セッションの
maxAge を要件に応じて短縮(デフォルト 30 日は長すぎる場合あり)。
callbacks.signIn で メール検証済みか確認(特に Google などの provider)。
- 本番ログに 個人情報・JWT・OAuth トークンを出力しない。
より体系的なセキュリティチェックは Next.js 公式の認証ラーニングコース や Auth.js GitHub リポジトリ の Issue・Discussion で議論されているセキュリティ勧告も合わせて確認することを推奨します。
よくある質問
Auth.js v5 と v4 (NextAuth.js) はどちらを使うべきですか?
新規プロジェクトは v5 を選んでください。v5 は単一 NextAuth() 呼び出しで API が統一され、Next.js 16 の proxy.ts やパスキーにネイティブ対応します。v4 は今後セキュリティパッチのみで、新機能は追加されません。
Next.js 16 では middleware.ts は使えなくなりますか?
Next.js 16 では middleware.ts が proxy.ts にリネームされました。Next.js 15 以下では引き続き middleware.ts を使います。Auth.js v5 はどちらにも対応しており、エクスポート行 export { auth as middleware } from "@/auth" は同じです。
JWT と Database セッションどちらを選ぶべきですか?
OAuth のみで即時ログアウトが不要なら JWT、マジックリンク・管理画面からの強制ログアウト・複数デバイス管理が必要なら Database を選びます。両者の中間として「アダプター + JWT」のハイブリッド構成も可能です。
AUTH_SECRET はどうやって生成しますか?
openssl rand -base64 32 または npx auth secret コマンドで 32 バイトのランダム値を生成します。値は環境ごとに別のものを用意し、.env.local に保存して Git にはコミットしないでください。
Auth.js v5 でパスキーは使えますか?
はい、v5 から next-auth/providers/passkey が公式追加されました。Database アダプターと experimental.enableWebAuthn: true が必要で、Face ID・Touch ID・YubiKey などで認証できます。SMS OTP より高い耐フィッシング性が得られます。