🥑

【Next.js】middlewareでBasic認証

2024/05/23に公開

はじめに

Basic認証を実装する機会があったので復習がてらまとめました。

Basic認証とは

Basic認証はWebサイトやアプリへのアクセスを制限する認証方法です。

ブラウザがユーザー名とパスワードを入力するように求めるポップアップを表示します。
これのことです。

Basic認証では、Authorizationヘッダに、「Basic」の文字と、入力されたユーザー名とパスワードから生成した認証情報を指定してサーバー側にリクエストします。

Basic認証の仕組み

  1. アクセス要求:ユーザーがページにアクセスしようとする
  2. 認証要求:サーバーがユーザーに認証を求め、ブラウザに「ユーザー名」と「パスワード」の入力を促す
  3. ユーザー入力:ユーザーが「ユーザー名」と「パスワード」を入力する
  4. 認証情報の送信:ブラウザが入力された情報をBase64でエンコードし、HTTPヘッダーに付与してサーバーに送信する
  5. 認証情報の確認:サーバーが受け取った認証情報をデコードし、正しいかどうかを確認する
  6. アクセス許可:認証情報が正しければ、ユーザーはリソースにアクセスできる

middlewareとは

Next.jsの機能の一つでリクエストがサーバーに到達する前に特定の処理を実行するために使用されます。
ここでいう「リクエスト」とはブラウザからサーバーに対して送られるHTTPリクエストのことです。
ユーザーが特定のページにアクセスするときのリクエストやAPIに対するリクエストが含まれます。

実装方法

ディレクトリ構成

不要な部分は省略していますが.envファイルとmiddleware.tsの位置関係は以下のようになります。

├── src
│   ├── app // ルーティング
│   └── middleware.ts 
├── .env
└── package.json

※Next.js(app Router)

.envファイル設定

プロジェクトルートディレクトリに.envファイルを作成します。

ENABLE_BASIC_AUTH=true
BASIC_AUTH_USERNAME=username
BASIC_AUTH_PASSWORD=password

middleware.ts

srcファイルを作成している場合はsrc/middleware.tsに作成します。

middleware.ts
import { NextRequest, NextResponse } from "next/server";

export const config = {
  matcher: ["/tasks"],
};

export function middleware(req: NextRequest) {
  console.log("ミドルウェア発動😃");

  // BASIC認証が有効でない場合はスキップする
  if (process.env.ENABLE_BASIC_AUTH !== "true") {
    return NextResponse.next();
  }

  // 環境変数の設定がない場合はスキップする
  if (
    process.env.BASIC_AUTH_USERNAME === undefined ||
    process.env.BASIC_AUTH_PASSWORD === undefined
  ) {
    return NextResponse.next();
  }

  // BASIC認証のチェック
  const basicAuth = req.headers.get("authorization");
  console.log("authorizationヘッダを確認😎", basicAuth);

  if (basicAuth) {
    const authValue = basicAuth.split(" ")[1];
    const [username, password] = Buffer.from(authValue, "base64")
      .toString()
      .split(":");
    console.log("認証情報確認😲", authValue, username, password);

    if (
      username === process.env.BASIC_AUTH_USERNAME &&
      password === process.env.BASIC_AUTH_PASSWORD
    ) {
      // BASIC認証に成功した場合、アクセスを許可する
      return NextResponse.next();
    }
  }

  // BASIC認証に失敗した場合、エラーを表示する
  console.log("認証失敗🙃");
  return NextResponse.json(
    { error: "Basic Auth Required" },
    {
      // eslint-disable-next-line quotes
      headers: { "WWW-Authenticate": 'Basic realm="Secure Area"' },
      status: 401,
    }
  );
}

処理の流れ

ログを出力して処理を追ってみました。

  1. ユーザーがページにアクセスするとブラウザはサーバーに対してHTTPリクエストを送る

  2. console.log("ミドルウェア発動😃");

  3. console.log("authorizationヘッダを確認😎", basicAuth); null

  4. console.log("認証失敗🙃");

  5. サーバーはステータスコード401で認証が必要なことを知らせ、WWW-AuthenticateヘッダーでBasic認証形式を要求する

  6. ブラウザが画面にポップアップを出力し、ユーザーにユーザー名、パスワードの入力を求める

  7. ユーザーがユーザー名パスワードを入力

  8. ブラウザは、入力されたユーザー名とパスワードをBase64でエンコードし、Authorizationヘッダーに付与して再びサーバーにHTTPリクエストを送る

  9. console.log("ミドルウェア発動😃");

  10. console.log("authorizationヘッダを確認😎", basicAuth);

  11. サーバーが受け取った認証情報をデコードし、正しいかどうかを確認する
    console.log("認証情報確認😲", authValue, username, password);

  12. 入力したユーザー名、パスワードが正しければステータスコード200を返す

  13. ブラウザは内容を表示する

Authorizationヘッダーが付与されていることが確認できました。

ログを見てわかるように一度webサーバーにリクエストをして認証を要求することによってブラウザがあのポップアップを出力しているみたいです。

~~~~~
return NextResponse.json(
    { error: "Unauthorized" },
    {
      headers: { "WWW-Authenticate": 'Basic realm="Secure Area"' }, // ここで認証要求!
      status: 401,
    }
  );

まとめ

今回はBasic認証のしくみについてまとめてみました。
認証周りは何度学習しても忘れてしまうのでしっかり覚えたいです。

Discussion