【コピペでOK】NextAuthの認証ミドルウェアとIP制限などのミドルウェアを併用する方法

Featured image of the post

はじめに

「NextAuth.jsのミドルウェア(認証)」×「その他のミドルウェア(IP制限など)」 の実装方法が調べても出てこなかったので、解決方法をまとめる😊

💡
自分なりに考えた方法なのでもっといい方法があればXにてご連絡いただけると嬉しいです!

今回のゴール

「NextAuth.jsのミドルウェア(認証)」と「その他のミドルウェア(今回はIP制限)」を両方実装する。

動作イメージ
IP制限に引っかかった場合

どのページにアクセスしても「アクセス拒否ページ」にリダイレクトされる。

Image in a image block

💡
【処理の流れ】
  1. IP制限のミドルウェアが実行される。

    →IP制限に引っかかると「アクセス拒否ページ」にリダイレクト。

    (次のミドルウェアは実行しない)

  2. 認証のミドルウェアは実行されない。

IP制限を通り抜けた場合

どのページにアクセスしても「ログインページ」にリダイレクトされる。

Image in a image block

💡
【処理の流れ】
  1. IP制限のミドルウェアが実行される。

    →IP制限に引っかからなければ次のミドルウェアへ。

  2. 認証のミドルウェアが実行される。

    →未ログイン状態なら「ログインページ」にリダイレクト。

    (もしログイン済みならリダイレクトされずにページが表示される)

結論

✅NextAuth.jsにはwithAuth関数(NextAuth.jsの認証ミドルウェア)が用意されている。

withAuthIP制限のミドルウェア連続で実行すればOK。

【疑問】認証とIP制限を両立したい

背景

以下の記事を参考に最小限の認証機能を作り終えた✨

ここでさらにWebアプリ全体にIP制限をかける(特定のIPアドレスしかアクセスできないくする)という機能を追加しようとした😊

疑問が生まれた

middleware.ts

export { default } from 'next-auth/middleware'

このシンプルなミドルウェアに、どうやってIP制限のミドルウェアを追加したらいいの?😫

【答え】ミドルウェアを連続で実行すればOK

ミドルウェアを連結する関数を作って、2つのミドルウェアを連続で実行すればいい!

解決方法

✅middleware.tsを修正するだけ。

【参考】修正前middleware.ts
export { default } from 'next-auth/middleware'

修正後のmiddleware.ts

コピペでOK!

IP制限の処理は適宜変えてもOK!

import { NextRequest, NextResponse, NextMiddleware, NextFetchEvent } from 'next/server'
// ✅withAuthをインポートする
import { NextRequestWithAuth, withAuth } from "next-auth/middleware"
export type MiddlewareFactory = (middleware: NextMiddleware) => NextMiddleware;

// ミドルウェアを連結する関数
export function chainMiddlewares(
  functions: MiddlewareFactory[] = [],
  index = 0
): NextMiddleware {
  const current = functions[index];
  if (current) {
    const next = chainMiddlewares(functions, index + 1);
    return current(next);
  }
  return () => NextResponse.next();
}

// 【ミドルウェア1】IP制限
function widthIpRestriction(middleware: NextMiddleware) {
  return async (request: NextRequest, event: NextFetchEvent) => {
		// ----------------------------------------------
		// ここから

		// ※事前にIPアドレスのホワイトリストを環境変数「IP_WHITELIST」に登録しておく必要あり(カンマ区切りで複数指定可)
    const IP_WHITELIST = process.env.IP_WHITELIST!.split(",");
    const res = NextResponse.next();

    // access-deniedページにはミドルウェアを適用しない
    if(request.nextUrl.pathname === '/access-denied'){
      return res;
    }
  
    // ipアドレスを取得
    let ip: string = request.ip ?? request.headers.get('x-real-ip') ?? '';
    
  
    // プロキシ経由の場合
    const forwardedFor = request.headers.get('x-forwarded-for')
  
    // プロキシ経由の場合は、プロキシのIPアドレスを取得
    if(!ip && forwardedFor){
      ip = forwardedFor.split(',').at(0) ?? 'Unknown'
    }
  
    // 取得したIPアドレスがホワイトリストに含まれているかチェックし、含まれていない場合はアクセス拒否
    if(!IP_WHITELIST.includes(ip)){
      return NextResponse.redirect(new URL('/access-denied', request.url)); // アクセス拒否のページにリダイレクト
    }

		// ここまではIP制限以外の処理に変えてもOK!
	  // ----------------------------------------------

		// 次のミドルウェアを実行
    return middleware(request, event);
  };
}

// ✅【ミドルウェア2】NextAuth.jsのミドルウェア(認証)
function widthLogin(middleware: NextMiddleware) {
  return async (request: NextRequest, event: NextFetchEvent) => {
    // NextAuth.jsの認証ミドルウェアを実行
    return withAuth(request as NextRequestWithAuth, event);
  };
}

// ✅ミドルウェアを連結
export default chainMiddlewares([widthIpRestriction,widthLogin]);
【解説】✅withAuthをインポートする

早速大事なポイント!

修正前はいきなりexportしていた。

// middleware関数をいきなりexport
export { default } from 'next-auth/middleware'

修正後は一旦importで受け取る。

// middleware関数をexportせずに、一旦withAuthという関数名で受け取る
import { withAuth } from "next-auth/middleware"

受け取ったwithAuth関数(NextAuth.jsの認証ミドルウェア)とIP制限のミドルウェアと連結するのが今回のゴール😊

(連結は後で解説する)

【解説】✅【ミドルウェア2】NextAuth.jsのミドルウェア(認証)

ミドルウェアを連結するための準備。

連結するために、ラップした関数を作っているだけ。

戻り値をwithAuth関数(NextAuth.jsの認証ミドルウェア)にしておくだけでいい。

【解説】✅ミドルウェアを連結

引数に渡したミドルウェアを連続して実行してくれる強化版ミドルウェア。

これで「IP制限」と「認証」のミドルウェアが順番に実行できる!

→今回のゴール✨

まとめ

  • NextAuth.jsからwithAuth関数(NextAuth.jsの認証ミドルウェア)を受け取ることができる。
  • withAuth関数(認証のミドルウェア)任意の関数(IP制限のミドルウェアなど)連続で実行すれば、「認証のミドルウェア」 × 「任意のミドルウェア」が実装できる。

参考サイト