Next.jsで認証の検証をミドルウェアで実装する

Next.jsで認証の検証をミドルウェアで実装する

ミドルウェアとは

ミドルウェアは、クライアントからのリクエストのサーバからのレスポンスの間に位置し、そのリクエストやレスポンスを処理するコードのこと。

通常、ミドルウェアは認証やログ、リクエストの修正、レスポンスの修正などの共通の機能を実装していく。

この記事では、Next.jsにおいて、ミドルウェアを使って特定のURLへのアクセス制御を行う実装について触れていきます。

Next.jsのApp Routerでミドルウェアを実装する方法

Next.jsでミドルウェアを実装するには、プロジェクトのルートにmiddleware.tsというファイルを作成する必要があります。

このファイル内で、リクエストを受け取り、必要な処理を行います。

ミドルウェアはNextRequestNextResponseを使って、リクエストとレスポンスを操作します。

以下に、特定のURLにアクセスしようとしたユーザがログイン済みかを確認し、ログインしていない場合はログイン画面に強制的に遷移させる実装例を示します。

import { NextResponse, type NextRequest } from "next/server";

export async function middleware(request: NextRequest) {
  let response = NextResponse.next({
    request: {
      headers: request.headers,
    },
  });

  const path = new URL(request.url).pathname;

  const user = <ユーザを取得する関数>

  if (
    (path === "/" ||
      path === "/example-page1" ||
      path === "/example-page2" ||
      path === "/example-page3" ||
      path === "/example-page4" ||
      path === "/example-page5") &&
    !user
  ) {
    return NextResponse.redirect(new URL("/login", request.url));
  }

  return response;
}

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)"],
};

実装の内容

ここでは、コードを上から順に分けて、それぞれ説明を加えていきます。

ミドルウェア関数の定義

export async function middleware(request: NextRequest)でミドルウェア関数を定義する。

NextRequestはリクエストオブジェクトで、リクエストの詳細情報にアクセスできる。

export async function middleware(request: NextRequest) {

レスポンスの初期化

NextResponse.nextを使って、デフォルトのレスポンスを作成する。

ここでは、リクエストヘッダを引き継いだレスポンスを初期化している。

let response = NextResponse.next({
  request: {
    headers: request.headers,
  },
});

リクエストされたパスの取得

new URL(request.url).pathnameを使って、リクエストされたURLのパスを取得する。

const path = new URL(request.url).pathname;

ユーザの取得

ユーザを取得する関数は、認証に使用しているサービスやCookie、色々あるので省かせていただきますが、ログインしているユーザを取得します。まだログインしていない場合は、nullが返ってくる想定です。

const user = <ユーザを取得する関数>

認証チェック

特定のパス(例:/example-page1/example-page2など)にアクセスしようとした際に、ユーザが認証済みでない場合、NextResponse.redirectを使って、ログインページ(/login)にリダイレクトさせています。

if (
    (path === "/" ||
      path === "/example-page1" ||
      path === "/example-page2" ||
      path === "/example-page3" ||
      path === "/example-page4" ||
      path === "/example-page5") &&
    !user
  ) {
    return NextResponse.redirect(new URL("/login", request.url));
  }

パスのマッチング設定

configオブジェクトで、ミドルウェアが適用されるパスを指定します。

ここでは、静的ファイルや画像、ファビコンなどの特定のパスを除外しています。

除外することで、どういったメリットがあるのでしょうか。結論、これらのファイルには認証を必要としないためです。

無駄なリダイレクトを避け、効率的にミドルウェアを動作させることができる。つまり、主にパフォーマンスの話になりますね。

以下のコードでは、これらのパスを除外しています。

  • _next/static
    Next.jsのビルドファイル
  • _next/image
    Next.jsの画像最適化エンドポイント
  • favicon.ico
    ファビコン
  • 画像ファイル拡張子
    .svg, .png, .jpg, .gif, .webp
export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)"],
};

まとめ

このようにして、Next.jsのミドルウェアを使って、特定のURLへのアクセスを制御して、ユーザがログインしていない場合はログインページにリダイレクトする機能を実装することができます。

これにより、認証が必要なページへのアクセスを制限し、アプリケーションのセキュリティを強化することができました。

この実装を通じて感じたのは、Next.jsのミドルウェアの柔軟性と便利さです。

特に、簡単にリクエストやレスポンスを操作できる点です。

特定のパスに対して条件付きで処理を行えるため、アプリケーションの認証やアクセス制御をシンプルかつ効果的に実装できると感じました。

今回は認証チェックを行いましたが、ミドルウェアを活用して色々なことができそうです。

私はまだまだNext.jsを学んでいるところなので、理解を間違えているところ、よりよい実装方法がありましたら、X(Twitter)でご連絡していただけると嬉しいです!