Clerk x Next.js でmiddlewareで関数を連結させる
はじめに
以下の記事で middleware.ts で複数の middleware の関数を連結させる方法を紹介しました。
ここでは、Clerk の authMiddleware を連結させる方法を紹介します。
下記が作業コードです。
実施内容
Middleware で3つの要素を実装します。
要素 | 説明 |
---|---|
IP制限 | 全てのアクセスに対して特定のIP制限実施します |
ログ出力 | 特定のパスへのアクセスに対してユーザーのリクエスト情報を出力する |
Clerk認証処理 | 特定のパスへのアクセスに対して認証情報を確認します |
作業環境を作成
以下で作成した環境を復元します。
Next.jsプロジェクトの新規作成し環境構築
作業するプロジェクトを新規に作成していきます。
長いので、折り畳んでおきます。
新規プロジェクト作成と初期環境構築の手順詳細
$ pnpm create next-app@latest nextjs-clerk-middleware-chain-sample --typescript --eslint --import-alias "@/*" --src-dir --use-pnpm --tailwind --app
$ cd nextjs-clerk-middleware-chain-sample
以下の通り不要な設定を削除し、プロジェクトの初期環境を構築します。
$ mkdir src/styles
$ mv src/app/globals.css src/styles/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;
export default function Home() {
return (
<main className="text-lg">
テストページ
</main>
)
}
import '@/styles/globals.css'
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="ja">
<body className="">{children}</body>
</html>
);
}
import type { Config } from "tailwindcss";
const config: Config = {
content: [
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
plugins: [],
};
export default config;
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
+ "baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
コミットします。
$ pnpm build
$ git add .
$ git commit -m "新規にプロジェクトを作成し, 作業環境を構築"
GitHub & Vercel にデプロイ
GitHub & Vercel にデプロイします。以降動作確認は、Vercel で行います。
IP制限、ログ出力を実装
IP 制限、ログ出力を middleware
に実装します。
長いので、折り畳んでおきます。
IP制限、ログ出力を実装
環境変数をファイルを作成します。アクセスを許可する IP アドレスを設定します。
$ touch .env.local
IP_WHITE_LIST=xxx.xxx.xxx.xxx, yyy.yyy.yyy.yyy
Middleware を実装します。
$ mkdir -p src/middlewares
$ touch src/middlewares/widthIpRestriction.ts \
src/middlewares/withLogging.ts \
src/middleware.ts
middlewares
の配下にIP制限とログ出力機能を実装します。
import {
NextFetchEvent,
NextMiddleware,
NextRequest,
NextResponse,
} from "next/server";
export function widthIpRestriction(middleware: NextMiddleware) {
return async (request: NextRequest, event: NextFetchEvent) => {
// ##################################################
// IP制限
//
// 全てのパスに対してIP制限を実行します。
// ##################################################
// ホワイトリストに登録されたIPリストを取得します。
const ipWhiteList = new Set(
process.env.IP_WHITE_LIST?.split(",").map((item: string) => {
return item.trim();
})
);
// ホワイトリストに登録されていないIPアドレスからのアクセスは拒否します。
if (request.ip && !ipWhiteList.has(request.ip as string)) {
const log = {
message: `許可されていないIPアドレスからのアクセスのためアクセスを拒否しました`,
ip: request.ip,
url: request.nextUrl.pathname,
method: request.method,
};
console.log(log);
return new NextResponse(null, { status: 401 });
}
return middleware(request, event);
};
}
import {
NextFetchEvent,
NextMiddleware,
NextRequest,
} from "next/server";
export function widthLogging(middleware: NextMiddleware) {
return async (request: NextRequest, event: NextFetchEvent) => {
// ##################################################
// ログ出力
//
// パスが以下の場合にログを出力します。
// ・"/"から始まり、"."を含まない任意のパス
// ・"/_nextから始まらない任意のパス
// ・"/"のルートパス。
// ・"/api"から始まる任意のパス
// ・"/trpc"から始まる任意のパス
// ##################################################
if (
request.nextUrl.pathname.match(/\/(?!.*\..*|_next).*/) ||
request.nextUrl.pathname.match(/\/(api|trpc)(.*)/) ||
request.nextUrl.pathname === "/"
) {
// リクエストの情報をJSON形式で出力します。
const log = {
ip: request.ip,
geo: request.geo,
url: request.nextUrl.pathname,
method: request.method,
};
console.log(JSON.stringify(log, (k, v) => (v === undefined ? null : v)));
}
return middleware(request, event);
};
}
middleware.ts
を定義します。
import { NextResponse } from "next/server";
import { widthLogging } from "@/middlewares/withLogging";
import { widthIpRestriction } from "@//middlewares/widthIpRestriction";
export default widthLogging(widthIpRestriction(() => NextResponse.next()));
コミットします。
$ pnpm build
$ git add .
$ git commit -m "IP制限、ログ出力を実装"
$ git push origin main
Clerkのアカウント作成
Clerk のアカウント作成はこちらを参照ください。
Clerkを設定
Next.js で Clerk が提供する SDK を利用するために、Clerk のパッケージをインストールします。
$ pnpm add @clerk/nextjs
環境変数を .env.local
に設定します。APIKEY はダッシュボードから取得します。
APIKEY を .env.local
に設定します。
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your_publishable_key
CLERK_SECRET_KEY=your_secret_key
どこからでもセッション、認証済みのユーザー情報などの認証情報にアクセスできるように、<ClerkProvider>
コンポーネントを設定します。layout.tsx
に <ClerkProvider>
を設定します。
+import { ClerkProvider } from "@clerk/nextjs";
import "./globals.css";
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
+ <ClerkProvider>
<html lang="ja">
<body className="bg-white">{children}</body>
</html>
+ </ClerkProvider>
);
}
アプリケーション全体を保護するため、認証用の middleware の関数を定義します。
- 「/」は公開用にします。
- 「/_next」から始まるパスは無視します。
- 「/」から「.」を含むファイルパスは無視します。
$ touch src/middlewares/withAuth.ts
import { authMiddleware } from "@clerk/nextjs";
export default authMiddleware({
publicRoutes: ["/"],
ignoredRoutes: ["/(.*\\..*)(.*)", "/(_next)(.*)"],
});
import { widthLogging } from "@/middlewares/withLogging";
import { widthIpRestriction } from "@//middlewares/widthIpRestriction";
import withAuth from "@/middlewares/withAuth";
export default widthLogging(widthIpRestriction(withAuth));
コミットします。
$ pnpm build
$ git add .
$ git commit -m "Clerkを設定"
$ git push origin main
Vercel 上で動作確認するときは、Clerk の環境変数ファイルを登録をもれないようにします。
まとめ
- authMiddleware を他の middleware 関数と連結させる方法を紹介しました。
下記が作業コードです。
Discussion