next-auth vs passport 徹底比較

next-auth の詳細passport の詳細
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

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) v5passport v0.7
GitHub Stars≈ 26k≈ 23k
初回リリース2020年2011年
対応フレームワークNext.js(SvelteKit, Express等にも拡張中)Express, Koa, Fastify 等ほぼ全て
認証方式OAuth / OIDC / Email / Credentials / WebAuthnStrategy パターンで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-authpassport
セッション管理自動(JWT or DB)express-session を自分で設定
シリアライズ/デシリアライズ不要自分で実装
コールバックURL自動生成 (/api/auth/callback/google)自分で定義・ルーティング
CSRF対策自動別途 csurf 等が必要
型安全性Session型を拡張可能、推論が効くreq.userany になりがち
ルート保護middleware.ts で一括制御各ルートにガードを付与

5. どちらを選ぶべきか — ユースケース別の推奨

✅ next-auth を選ぶべきケース

ユースケース理由
Next.js(App Router)でSaaSを構築Server Components・middleware との統合が圧倒的に楽
OAuth/ソーシャルログインが中心プロバイダー追加が数行で完了
Vercel / Edge環境にデプロイEdge Runtime対応済み
認証まわりの工数を最小化したいセッション・CSRF・コールバックが全て組み込み
個人〜中規模チームで素早くリリースしたい設定ファイル1つで認証が完成する

✅ passport を選ぶべきケース

ユースケース理由
Express / Fastify / NestJS ベースのAPINext.js以外のNode.jsフレームワークでは事実上の標準
SAML / LDAP / 社内SSO連携が必要エンタープライズ向けStrategyが豊富
認証フローを完全にカスタマイズしたいリダイレクト・エラー処理・多段階認証を細かく制御可能
マイクロサービスのAPI Gatewayフレームワーク非依存で認証層だけを切り出せる
既存のExpressアプリに認証を追加既存のミドルウェアチェーンに自然に組み込める

🤔 どちらでもないケース

ユースケース推奨
Firebase / Supabase を使っている各サービスの組み込み認証を使う方が効率的
認証をマネージドサービスに任せたいAuth0, Clerk, WorkOS などのSaaSを検討
Deno / Bun ネイティブで構築各ランタイムのエコシステムを確認

6. まとめ

next-authpassportは「どちらが優れているか」ではなく、解決する課題のレイヤーが異なるライブラリです。

  • next-auth(Auth.js) は「Next.jsアプリケーションに認証を最速で組み込む」ためのフレームワーク統合型ソリューションです。セッション管理、CSRF保護、OAuthコールバック処理まで含めた「認証体験」をパッケージとして提供します。

  • passport は「あらゆるNode.jsアプリケーションに認証ミドルウェア