はじめに
「NextAuth.jsのミドルウェア(認証)」×「その他のミドルウェア(IP制限など)」 の実装方法が調べても出てこなかったので、解決方法をまとめる😊
今回のゴール
✅「NextAuth.jsのミドルウェア(認証)」と「その他のミドルウェア(今回はIP制限)」を両方実装する。
動作イメージ
IP制限に引っかかった場合
どのページにアクセスしても「アクセス拒否ページ」にリダイレクトされる。
-
IP制限のミドルウェアが実行される。
→IP制限に引っかかると「アクセス拒否ページ」にリダイレクト。
(次のミドルウェアは実行しない)
- 認証のミドルウェアは実行されない。
IP制限を通り抜けた場合
どのページにアクセスしても「ログインページ」にリダイレクトされる。
-
IP制限のミドルウェアが実行される。
→IP制限に引っかからなければ次のミドルウェアへ。
-
認証のミドルウェアが実行される。
→未ログイン状態なら「ログインページ」にリダイレクト。
(もしログイン済みならリダイレクトされずにページが表示される)
結論
✅NextAuth.jsにはwithAuth
関数(NextAuth.jsの認証ミドルウェア)が用意されている。
✅withAuth
とIP制限のミドルウェア
を連続で実行すれば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制限のミドルウェアなど)
を連続で実行すれば、「認証のミドルウェア」 × 「任意のミドルウェア」が実装できる。