next-auth vs passport — Next.js時代の認証ライブラリ、どちらを選ぶべきか?
1. 結論
Next.jsアプリケーションを構築しているならnext-auth(現Auth.js)を第一候補にすべきです。 Express/Fastifyなど非Next.jsのNode.jsサーバーや、認証フローを細部までカスタマイズしたい場合はpassportが依然として強力な選択肢です。両者は競合というより「レイヤーが異なるライブラリ」であり、プロジェクトのアーキテクチャに応じて使い分けるのが正解です。
2. 比較表
| 観点 | next-auth (Auth.js) v5 | passport v0.7 |
|---|---|---|
| GitHub Stars | ≈ 26k | ≈ 23k |
| 初回リリース | 2020年 | 2011年 |
| 対応フレームワーク | Next.js(SvelteKit, Express等にも拡張中) | Express, Koa, Fastify 等ほぼ全て |
| 認証方式 | OAuth / OIDC / Email / Credentials / WebAuthn | Strategy パターンで500以上の認証方式 |
| セッション管理 | JWT / Database セッション(組み込み) | 自前実装 or express-session 等が必要 |
| TypeScript対応 | ◎ ネイティブ対応・型定義充実 | △ @types/passport あり、型が緩い |
| バンドルサイズ (install) | 約 180KB(コア) | 約 30KB(コア)+ Strategy 個別追加 |
| 学習コスト | 低〜中(Next.js前提知識が必要) | 中〜高(ミドルウェア設計の理解が必要) |
| DB連携 | Prisma / Drizzle / TypeORM 等の公式Adapter | 自前実装 |
| CSRF保護 | 組み込み | 自前実装 |
| React Server Components対応 | ◎ ネイティブ対応 | × 非対応 |
| Edge Runtime対応 | ◎ v5で対応 | × 非対応 |
| メンテナンス状況(2025年) | 活発(Auth.jsへのリブランド進行中) | 安定(低頻度だが継続) |
3. それぞれの強み
next-auth(Auth.js)の強み
- ゼロコンフィグに近いOAuth統合: Google, GitHub, Discord など80以上のプロバイダーを数行で追加できます
- Next.js App Routerとの深い統合:
middleware.tsでのルート保護、Server Componentsでのセッション取得がシームレスです - セッション管理が組み込み: JWT戦略とDatabase戦略を設定一つで切り替えられ、CSRF保護も自動で付与されます
- Edge Runtime対応: Vercel Edge Functions や Cloudflare Workers でも動作します
- DB Adapterエコシステム: Prisma, Drizzle, Supabase, MongoDB など公式Adapterが豊富で、ユーザーテーブルの設計を標準化できます
passportの強み
- フレームワーク非依存: Express, Fastify, Koa, NestJS など、あらゆるNode.jsフレームワークで使えます
- Strategy パターンの柔軟性: 500以上のStrategyが公開されており、SAML, LDAP, カスタムAPIトークンなどエンタープライズ要件にも対応できます
- 認証フローの完全な制御: リダイレクトURL、エラーハンドリング、多段階認証の各ステップを細かく制御できます
- 軽量なコア: 認証のミドルウェア層のみを提供し、セッション管理やDB層は自由に選択できます
- 13年以上の実績: 膨大なドキュメント、ブログ記事、Stack Overflowの回答が蓄積されています
4. コード例で比較
タスク: Google OAuth認証を実装し、ログインユーザーの情報を取得する
next-auth(Auth.js v5 + Next.js App Router)
// auth.ts — 認証設定(プロジェクトルート)
import NextAuth from "next-auth";
import Google from "next-auth/providers/google";
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
Google({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isProtected = nextUrl.pathname.startsWith("/dashboard");
if (isProtected && !isLoggedIn) {
return Response.redirect(new URL("/login", nextUrl));
}
return true;
},
},
});
// app/api/auth/[...nextauth]/route.ts — APIルート
import { handlers } from "@/auth";
export const { GET, POST } = handlers;
// middleware.ts — ルート保護
export { auth as middleware } from "@/auth";
export const config = {
matcher: ["/dashboard/:path*"],
};
// app/dashboard/page.tsx — Server Componentでセッション取得
import { auth } from "@/auth";
import { redirect } from "next/navigation";
export default async function DashboardPage() {
const session = await auth();
if (!session?.user) {
redirect("/login");
}
return (
<div>
<h1>ダッシュボード</h1>
<p>ようこそ、{session.user.name}さん</p>
<img src={session.user.image ?? ""} alt="avatar" width={48} height={48} />
<p>メール: {session.user.email}</p>
</div>
);
}
// app/login/page.tsx — ログインページ
import { signIn } from "@/auth";
export default function LoginPage() {
return (
<form
action={async () => {
"use server";
await signIn("google", { redirectTo: "/dashboard" });
}}
>
<button type="submit">Googleでログイン</button>
</form>
);
}
ファイル数: 4〜5ファイル、実質的な認証ロジック: 約40行
passport(Express + TypeScript)
// src/config/passport.ts — Passport設定
import passport from "passport";
import { Strategy as GoogleStrategy, Profile } from "passport-google-oauth20";
interface User {
id: string;
displayName: string;
email: string;
avatar: string;
}
// インメモリストア(本番ではDBを使用)
const users = new Map<string, User>();
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
callbackURL: "/auth/google/callback",
},
(
accessToken: string,
refreshToken: string,
profile: Profile,
done: (error: Error | null, user?: User) => void
) => {
const user: User = {
id: profile.id,
displayName: profile.displayName,
email: profile.emails?.[0]?.value ?? "",
avatar: profile.photos?.[0]?.value ?? "",
};
users.set(user.id, user);
done(null, user);
}
)
);
passport.serializeUser((user: User, done) => {
done(null, user.id);
});
passport.deserializeUser((id: string, done) => {
const user = users.get(id);
done(null, user ?? null);
});
export default passport;
// src/middleware/auth.ts — 認証ガード
import { Request, Response, NextFunction } from "express";
export function ensureAuthenticated(
req: Request,
res: Response,
next: NextFunction
): void {
if (req.isAuthenticated()) {
return next();
}
res.redirect("/login");
}
// src/app.ts — Expressアプリケーション
import express from "express";
import session from "express-session";
import passport from "./config/passport";
import { ensureAuthenticated } from "./middleware/auth";
const app = express();
// セッション設定(自前で管理が必要)
app.use(
session({
secret: process.env.SESSION_SECRET!,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === "production",
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, // 24時間
},
})
);
app.use(passport.initialize());
app.use(passport.session());
// 認証ルート
app.get(
"/auth/google",
passport.authenticate("google", { scope: ["profile", "email"] })
);
app.get(
"/auth/google/callback",
passport.authenticate("google", { failureRedirect: "/login" }),
(req, res) => {
res.redirect("/dashboard");
}
);
// ログアウト
app.post("/auth/logout", (req, res, next) => {
req.logout((err) => {
if (err) return next(err);
res.redirect("/");
});
});
// 保護されたルート
app.get("/dashboard", ensureAuthenticated, (req, res) => {
const user = req.user as {
displayName: string;
email: string;
avatar: string;
};
res.json({
message: `ようこそ、${user.displayName}さん`,
email: user.email,
avatar: user.avatar,
});
});
// ログインページ
app.get("/login", (req, res) => {
res.send('<a href="/auth/google">Googleでログイン</a>');
});
app.listen(3000, () => {
console.log("Server running on http://localhost:3000");
});
ファイル数: 3ファイル、実質的な認証ロジック: 約100行
コード比較のポイント
| 観点 | next-auth | passport |
|---|---|---|
| セッション管理 | 自動(JWT or DB) | express-session を自分で設定 |
| シリアライズ/デシリアライズ | 不要 | 自分で実装 |
| コールバックURL | 自動生成 (/api/auth/callback/google) | 自分で定義・ルーティング |
| CSRF対策 | 自動 | 別途 csurf 等が必要 |
| 型安全性 | Session型を拡張可能、推論が効く | req.user が any になりがち |
| ルート保護 | middleware.ts で一括制御 | 各ルートにガードを付与 |
5. どちらを選ぶべきか — ユースケース別の推奨
✅ next-auth を選ぶべきケース
| ユースケース | 理由 |
|---|---|
| Next.js(App Router)でSaaSを構築 | Server Components・middleware との統合が圧倒的に楽 |
| OAuth/ソーシャルログインが中心 | プロバイダー追加が数行で完了 |
| Vercel / Edge環境にデプロイ | Edge Runtime対応済み |
| 認証まわりの工数を最小化したい | セッション・CSRF・コールバックが全て組み込み |
| 個人〜中規模チームで素早くリリースしたい | 設定ファイル1つで認証が完成する |
✅ passport を選ぶべきケース
| ユースケース | 理由 |
|---|---|
| Express / Fastify / NestJS ベースのAPI | Next.js以外のNode.jsフレームワークでは事実上の標準 |
| SAML / LDAP / 社内SSO連携が必要 | エンタープライズ向けStrategyが豊富 |
| 認証フローを完全にカスタマイズしたい | リダイレクト・エラー処理・多段階認証を細かく制御可能 |
| マイクロサービスのAPI Gateway | フレームワーク非依存で認証層だけを切り出せる |
| 既存のExpressアプリに認証を追加 | 既存のミドルウェアチェーンに自然に組み込める |
🤔 どちらでもないケース
| ユースケース | 推奨 |
|---|---|
| Firebase / Supabase を使っている | 各サービスの組み込み認証を使う方が効率的 |
| 認証をマネージドサービスに任せたい | Auth0, Clerk, WorkOS などのSaaSを検討 |
| Deno / Bun ネイティブで構築 | 各ランタイムのエコシステムを確認 |
6. まとめ
next-authとpassportは「どちらが優れているか」ではなく、解決する課題のレイヤーが異なるライブラリです。
-
next-auth(Auth.js) は「Next.jsアプリケーションに認証を最速で組み込む」ためのフレームワーク統合型ソリューションです。セッション管理、CSRF保護、OAuthコールバック処理まで含めた「認証体験」をパッケージとして提供します。
-
passport は「あらゆるNode.jsアプリケーションに認証ミドルウェア