【Next.js15.4】Next.js MiddlewareとOAuth認証について色々考えてみた

2025-07-26

個人開発のプロダクトでNext.js Middlewareでデータベースセッション認証を実装しようとして、「PrismaがNext.js Middlewareで動かない」という壁にぶつかりました。

何が問題なのか?

Next.js MiddlewareはNode.jsではなくEdge RuntimeなのでPrismaを使ったDBセッション認証が行えない

結論

セッション管理をjwtで、トークン管理をDBで行うハイブリッド方式を使うか、Node.js Middlewareを使う

前提となる技術構成

そもそも、バックエンドAPIを持たないフロントエンド単体の構成であれば、OAuthの各種トークンはインメモリやブラウザでセキュアに管理すれば十分ですし、Next.js Middlewareを使わない構成であればDBセッションで何も問題ありません。

// バックエンドなしならクライアントサイドで完結
const { data: session } = useSession()
if (!session) {
  router.push('/login')
  return
}

// Middlewareを使わないならDBセッションで問題なし
// pages/_app.js や layout.tsx で認証チェック
const session = await getServerSession(authOptions)
if (!session) {
  redirect('/login')
}

しかし、今回はユーザーのカレンダーデータにアクセスするというセキュリティに特に留意が必要なプロダクトです。
DBでのトークン管理やNext.js Middlewareといった推奨されていたり用意されている方法を取りたいものです。

そこで、Next.js MiddlewareでPrismaのクエリを打ってDBからトークンを取ってこようと考えるのが自然な流れですが、Next.jsのMiddlewareはNode.jsではなくEdge Runtime(軽量のJSランタイム)で動作するため、PrismaをはじめとするORMを直接利用できません。

そこで、以下の2つの方法を考えることができます。

1. ハイブリッド認証方式 - JWTセッション・DBトークン方式

最も実用的なアプローチは、セッション管理とトークン管理を分離することです。

技術構成

  • セッション情報:JWTとしてブラウザクッキーに保存
  • 外部APIトークン:PostgreSQL(Supabase)のDBに保存
  • 認証ライブラリ:Auth.js + Prisma Adapter

実装の例

// auth.ts
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"

export default NextAuth({
  providers: [GoogleProvider({...})],
  strategy: "jwt", // セッションはJWT
  callbacks: {
    async jwt({ token, account }) {
      // 外部トークンはDBに保存
      if (account) {
        await saveTokenToDatabase(token.sub, account.access_token)
      }
      return token
    }
  }
})

// middleware.ts
import { NextResponse } from 'next/server';
import { NextRequest } from 'next/server';
import { auth } from '../auth';

// ログイン不要なパス
const publicPaths = ['/login', '/', '/terms', '/privacy','/image','/test'];

export async function middleware(request: NextRequest) {
  // 現在のパスを取得
  const path = request.nextUrl.pathname;
  
  // 現在のパスが公開パスかどうかをチェック
  const isPublicPath = publicPaths.some(publicPath => 
    path === publicPath || 
    path.startsWith(`${publicPath}/`)
  );

  // 静的アセットなどのパスはスキップ
  if (
    path.startsWith('/_next') || 
    path.includes('favicon.ico') ||
    path.startsWith('/api/auth')
  ) {
    return NextResponse.next();
  }

  // 認証状態を確認
  const session = await auth();
  const isAuthenticated = !!session;
  
  // デバッグ用ログ
  console.log(`Path: ${path}, Auth: ${isAuthenticated}, Public: ${isPublicPath}`);

  // 未認証ユーザーが保護されたページにアクセスしようとした場合
  if (!isAuthenticated && !isPublicPath) {
    const loginUrl = new URL('/login', request.url);
    // 元のURLをクエリパラメータとして保存
    loginUrl.searchParams.set('callbackUrl', path);
    return NextResponse.redirect(loginUrl);
  }

  // 認証済みユーザーがログインページにアクセスしようとした場合
  if (isAuthenticated && path === '/login') {
    return NextResponse.redirect(new URL('/myPage', request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico).*)',
  ],
};

この方式では、middleware.tsでJWTの検証のみを行い、実際の外部APIトークンが必要な処理はサーバーコンポーネントやAPI Routesで実行します。

メリット

  • Edge Runtimeの高速性を活用
  • CDNでの分散実行による低レイテンシ
  • シンプルな実装

懸念点:JWT特有の脆弱性 ステートレス認証のため、以下の点に注意が必要です:

  • 有効期限内であればログアウト後もトークンが使用可能
  • セキュリティ強化のため、トークンブラックリスト等の実装を検討

2. Node.js Middleware

2025年7月現在、「middlewareでDBセッション使用不可問題」はコミュニティでも長らく議論されてきました。

Next.js 15.2で実験的機能として導入されたNode.js Middlewareが、Next.js 15.4で安定版に昇格しました。これにより、MiddlewareでPrismaを直接使用できるようになります。

// middleware.ts

...

export const config = {
  runtime: 'nodejs', // Node.js runtimeを指定
  matcher: ['/dashboard/:path*', '/profile/:path*']
}

メリット

  • セキュリティ上の最適解
  • 従来のNode.jsライブラリがそのまま使用可能
  • セッションの即座無効化が可能

トレードオフ

  • Edge Runtimeの軽量性は失われる
  • CDNでの分散実行による速度向上の恩恵が減少

まとめ

認証について考えるいい機会となりました。

参考

next-auth(Auth.js) Discussions #4265
Support middleware for session-based authentication - 2022/5/25

next.js Discussions #71727
Node.js runtime support for Next.js Middleware - 2024/10/23

stackoverflow
How to secure routes using next-auth and middleware with database strategy - 2023/6/17

© 2025 SoraPort All Rights Reserved.