🌐

Next.js 13:Basic認証の実装方法とつまづいた点について

2022/12/25に公開
2

はじめに

Next.js 13をつかってWebアプリケーションをつくった際に、Basic認証をつける必要がありました。
Basic認証を実装する上でいくつかつまったポイントがあったので、実装方法やつまづいた点について書き残します。

実行環境

  • Next.js : 13.1.0
  • JavaScript

サンプルコード

まずサンプルコードを紹介します。
サンプルコードはNext.jsで用意されているcreate-next-appコマンドでアプリケーションを作成しました。そのため、説明に必要なファイル・フォルダのみでディレクトリ構造をあらわしたものが下記になります。

ディレクトリ構造
.
├── public
├── styles
├── pages
│   ├── api
│   │   └── auth.js
│   ├── index.js
│   └── test
│       └── index.js
└── middleware.js
middleware.js
middleware.js
import { NextRequest, NextResponse } from 'next/server'

/*
matcher : middleware.jsを適用する(呼び出す)パスを指定する
*/
export const config = {
    matcher: ['/:path*', '/test/:path*'],
}

export function middleware(req) {
    const basicAuth = req.headers.get('authorization')
    const url = req.nextUrl

    if (basicAuth) {
        const authValue = basicAuth.split(' ')[1]
	const [user, pwd] = Buffer.from(authValue, 'base64').toString().split(':');

        if (user === "test" && pwd === "test") {
            return NextResponse.next()
        }
    }
    url.pathname = '/api/auth'

    return NextResponse.rewrite(url)
}
auth.js
auth.js
export default function handler(req, res) {
    res.setHeader('WWW-authenticate', 'Basic realm="Secure Area"')
    res.statusCode = 401
    res.end(`Auth Required.`)
}

動作させるとこのようになります。

middlewareを設定する

Next.jsのmiddlewareとは公式ページではこのように説明されています。

Middleware allows you to run code before a request is completed, then based on the incoming request, you can modify the response by rewriting, redirecting, modifying the request or response headers, or responding directly.
Middleware runs before cached content, so you can personalize static files and pages. Common examples of Middleware would be authentication, A/B testing, localized pages, bot protection, and more. Regarding localized pages, you can start with i18n routing and implement Middleware for more advanced use cases.
引用:Next.js Middleware

middlewareをつかうことでリクエストが完了する前にコードを実行できるので、あるページを表示する前にBasic認証を表示させることができます。

またmiddlewareを導入するにあたって頭を悩ませた点が2点あります。

  • middleware.jsのファイル名
  • middleware.jsを置く位置

middleware.jsのファイル名

本記事ではNext.js 13を使用しています。
Next.js 12ではmiddleware.jsのファイル名の先頭に_middleware.jsとアンダースコアをつける必要ありましたがNext.js 13では必要ありません。

middleware.jsを置く位置

公式ドキュメントにはこのように記述があります。

Create a middleware.ts (or .js) file at the root or in the src directory (same level as your pages)

またmiddleware.jsは今回のサンプルコードのようにプロジェクトファイルのルートディレクトリ直下に置く、もしくはpagesフォルダと同じ階層に置く必要があります。

他のブログではpagesフォルダ直下に置いているmiddleware.jsを置いている例がよく見られました。pagesフォルダ直下の場合、middlewareが動作しなかったので、要注意です。

同様の投稿内容がstackoverflowやzennで見つかりました。
もしかしたら、他にも同じことで頭を悩ませている方がいそうですね.

https://stackoverflow.com/questions/73040090/nextjs-middleware-does-not-seem-to-be-triggered

https://zenn.dev/shotam/articles/0e05b4f41246d7

なので当たり前のことですが、Next.jsのバージョンによって仕様が変更する可能性があるので公式ドキュメントをよく確認したほうが良いでしょう。

middlewareの特定のパスで実行する

matcherをで特定のパスを指定することで、middlewareを実行するパスを指定できます。
今回のサンプルコードの場合、pages/index.jspages/test/index.jsで記述したページを開くときにBasic認証が表示されます。

export const config = {
    matcher: ['/:path*', '/test/:path*'],
}

注意点は、公式ドキュメントで記述されているようにパスのはじめは「/」から始める必要があることです。

Basic認証

Basic認証の処理は下記のようになっています。

middleware.js
export function middleware(req) {
    const basicAuth = req.headers.get('authorization')
    const url = req.nextUrl

    if (basicAuth) {
        const authValue = basicAuth.split(' ')[1]
	const [user, pwd] = Buffer.from(authValue, 'base64').toString().split(':');

        if (user === "test" && pwd === "test") {
            return NextResponse.next()
        }
    }
    url.pathname = '/api/auth'

    return NextResponse.rewrite(url)
}

認証に成功した場合、本来のリクエストを完了させます。

if (user === "test" && pwd === "test") {
   return NextResponse.next()
}

認証に失敗した場合、pages/api/auth.jsに記述したメッセージを表示されるようにしています。

middleware.js
url.pathname = '/api/auth'
return NextResponse.rewrite(url)

ユーザー名とパスワードの環境変数をつかって記述

.envファイルをつかって環境変数としてユーザー名とパスワードを記述しておくと管理が楽です。
https://nextjs-ja-translation-docs.vercel.app/docs/basic-features/environment-variables

ディレクトリ構造
.
├── public
├── styles
├── pages
│   ├── api
│   │   └── auth.js
│   ├── index.js
│   └── test
│       └── index.js
└── middleware.js
└── .env.local
.env.local
BASIC_USER=test
BASIC_PW=test
middleware.js
if (user === process.env.BASIC_USER && pwd === process.env.BASIC_PW) {
   return NextResponse.next()
}

その他:ブラウザでBasic認証の認証情報をリセット

これでBasic認証の動作をブラウザで繰り返し試したいときがあります
しかし一度、BASIC認証が成功するとブラウザでは認証情報が保持され、次回のアクセスではBasic認証が省略されます。
Basic認証をブラウザを閉じずに認証情報をリセットするには、一度、ホスト名の前に【任意の文字】@をつけてアクセス(例:http://【任意の文字】@example.com)します。
これでBasic認証の動作を繰り返し試すことができます。
より詳細な内容は下記が参考になりました。

https://dev.classmethod.jp/articles/delete-cache-for-basic-authentication/

おわりに

Next.js 13をつかったBasic認証の実装方法と実装途中でつまづいた点について紹介しました。
Next.jsのバージョンによっては仕様が異なるので、その都度、公式ドキュメントに目を通すことが重要ですね。

Discussion

Kosuke OgawaKosuke Ogawa

とても参考になりました。ありがとうございます。一部
midlware となっている箇所は
middleware が正しいでしょうか?