🤮

NextAuth.jsのMiddlewareに関する課題と解決策

2024/08/16に公開

初めに

最近、管理コンソールをリプレースするプロジェクトに取り組んでいます。その中で、フロントエンドの認証機能を実現するために、簡単にOAuth認証ができ、ログイン状態の管理が容易でセキュリティ面でも安全とされるAuth.js(旧NextAuth.js)を導入しました。
無知の状態から学びながらコツコツと実装を進めていく中で、基本的な機能が不足していたり、次から次へと問題が発生したりして、このツールが本当に適切なのか疑問に感じるようになりました。
本記事では、その疑問の中で特にmiddlewareに関連して直面した問題を共有します。もし誤りや改善点がありましたら、コメント等でご指摘いただけると幸いです。

やりたいこと

システムの特性上、常にログイン状態を求め、どのページにアクセスしても認証されたユーザーのみが閲覧できるようにする必要があります。そのため、NextAuthを使用してメールアドレス(ID)とパスワードを利用したCredentials Providerによる認証を実装しました。さらに、認証されていないユーザーがアクセスした場合はログインページへリダイレクトさせる機能も必要があり、Next.jsのMiddlewareを使用し、すべてのリクエスト認証が必要ないページ(ログインページ自体やパブリックなページは除外)で認証状態を確認する仕組みを考えました。

試したこと

まず、認証の実装はこちらの公式サイトに沿って以下のように実装を進めました。

  1. NextAuthをインストールします
npm install next-auth
  1. NextAuthの設定
    認証の定義と認証後のコールバックを設定し、認証データをセッションで管理できるように設定しました
//src/app/api/auth/[...nextauth].js
import CredentialsProvider from "next-auth/providers/credentials"
...
providers: [
  CredentialsProvider({
    name: 'Credentials',
    credentials: {
      email: { label: "Username", type: "email"},
      password: { label: "Password", type: "password" }
    },
    async authorize(credentials, req) {
      const res = await fetch("/test/endpoint", {
        method: 'POST',
        body: JSON.stringify(credentials),
        headers: { "Content-Type": "application/json" }
      })
      const data = await res.json();
      if (res.ok && data) {
        return data
      }
      return null
    }
  })
],
callbacks: {
  async jwt({ token, user }) {
    if (user) {
      token = user;
    }
      return token;
    },
  },
  async session({ session, token, user }) {
    session.user = token;
    return session;
  },
}
...
  1. Middlewareでは、NextAuthのセッション情報を取得するためにgetServerSessionを使用し、認証状態を確認します。そして、認証セッションがない場合はログインページにリダイレクトさせます
//src/middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const session = await getServerSession(req, res, authOptions)
  if (!session) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  const response = NextResponse.next();
  return response;
}

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

直面した問題

上記の設定を実行すると、以下のような謎のサーバエラーが発生しました。

Cannot read properties of undefined (reading 'substring')

この問題について調査したところ、Githubにissueによれば、このエラーはEdge Runtime環境でgetServerSessionが正常に動作していないことが原因であると判明しました。幸いなことに、この問題はNextAuthのv5で解決されたようです。そこで、最新のv5を取り入れることにしました。

解決案

NextAuth v5では、Edge-compatible(Edge Runtimeに対応した)auth()メソッドが導入されました。このメソッドを使用することで、Edge Runtime環境でもセッション管理がスムーズに行えるようになります。
▶︎ 関連公式ドキュメント

  1. 最新のNextAuth v5のbetaバージョンをインストール
npm install next-auth@beta
  1. getServerSession()の代わりにauth()を使用
// src/middleware.ts
import { NextResponse } from 'next/server';
import { auth } from "./auth";

export async function middleware(request) {
  const session = await auth();
  if (!session) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  return NextResponse.next();
}

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

Discussion