841-biborokuWebフロントエンドの備忘録

Cloudflare Pages functionの_middleware.tsでBasic認証をかける方法


以前、Honoを使ったBasic認証を試しましたが、今回はCloudflareで紹介されている記事と参考サイト「Cloudflare PagesにBasic認証をかけた話」のサンプルコードをもとに_middleware.tsでBasic認証を実装してみました。

/functions/_middleware.ts

const errorHandler = async (context:{
  next: () => Promise<Response>
}) => {
  try {
    const { next } = context;
    return await next();
  } catch (err: any) {
    return new Response(`${err.message}\n${err.stack}`, { status: 500 });
  }
};

const guardByBasicAuth = async (context:{
    next: () => Promise<Response>;
    request: Request;
    env: {
      BASIC_AUTH_USER: string
      BASIC_AUTH_PASSWORD: string
      ALLOW_BASIC_HOST: string
    };
  }
): Promise<Response> => {

  const { request, next, env } = context;

  const BASIC_AUTH_USER = env.BASIC_AUTH_USER;
  const BASIC_AUTH_PASSWORD = env.BASIC_AUTH_PASSWORD;
  const ALLOW_BASIC_HOST = env.ALLOW_BASIC_HOST;

  const headers: Headers = request.headers;
  const authorization: string | null = headers.get('authorization') ?? null;

  const host = headers.get('host');
  if( host === ALLOW_BASIC_HOST) return await next();

  if (!authorization) {
    return new Response(
      "You need to login.",
      {
        status: 401,
        headers: {
          'WWW-Authenticate': 'Basic realm="Input username and password"',
        },
      });
  };

  const [scheme, encoded] = authorization.split(' ');
  if (!encoded || scheme !== 'Basic') {
    return new Response(
      "Malformed authorization header.",
      {
        status: 400,
      }
    );
  }

  const buffer = Uint8Array.from(atob(encoded), character => character.charCodeAt(0));
  const decoded = new TextDecoder().decode(buffer).normalize();
  const index = decoded.indexOf(":");
  if (index === -1 || /[\0-\x1F\x7F]/.test(decoded)) {
    return new Response(
      "Invalid authorization value.",
      {
        status: 400,
      }
    );
  }

  const username = decoded.substring(0, index);
  const password = decoded.substring(index + 1);
  if (username !== BASIC_AUTH_USER || password !== BASIC_AUTH_PASSWORD) {
    return new Response(
      "Invalid username or password.",
      {
        status: 401,
      }
    );
  }
  return await next();
};

export const onRequest = [errorHandler, guardByBasicAuth];

環境変数を設定/wrangler.toml

  • Basic認証用のユーザ名とパスワード
  • Basic認証を除外したいホスト名


※wrangler.tomlは.gitignoreで除外します


wrangler.toml

BASIC_AUTH_USER = "xxx"
BASIC_AUTH_PASSWORD = "xxx"
ALLOW_BASIC_HOST="xxx"

envの値が取得できない

process.env.BASIC_AUTH_USER

env.BASIC_AUTH_USER

dotenvでもうまくいかず。


onRequestではどんな引数が使える・?


onRequestメソッドで渡されるオブジェクトのプロパティが公式ドキュメントにありました。

https://developers.cloudflare.com/pages/platform/functions/api-reference/#eventcontext


const guardByBasicAuth = async( next, request, env) =>


型をつける

next,request,envの型付けは以下の参考サイトをもとに設定しました。

Password Protection for Cloudflare Pages


try~catchのerrorはどうすればいい?

unknowを指定してもvscodeで波線が消えません。


参考:

いつの間にか、try { } catch (error) { } の errorが `unknown` になってしまう問題。

TypeScriptでcatch句の変数(error.message)を使おうとするとVSCodeに怒られる件


本当は型チェックをして処理~をしなければ、ですが今回はanyで対応しました。

ヘッダー情報host/authorizationに型付け

Headers - Web APIs | MDN

interface Headers
This Fetch API interface allows you to perform various actions on HTTP request and response headers. These actions include retrieving, setting, adding to, and removing. A Headers object has an associated header list, which is initially empty and consists of zero or more name and value pairs. You can add to this using methods like append() (see Examples.) In all methods of this interface, header names are matched by case-insensitive byte sequence.


const headers: Headers = request.headers;
const authorization: string | null = headers.get('authorization') ?? null;

ローカルでBasic認証がかかるか確認

package.jsonにscriptのエイリアス登録して実行してましたが、もっと早くプレビューできる方法があるようです。


変更前

package.jsonに設定していた以下を実行

"wrangler": "next build && next export && npx wrangler pages dev ./out",

npm run wrangler

ビルドもするため時間がかかります。


変更後

npx wrangler pages dev next dev

かなり早く実行されるようになりました。


公式ドキュメント:​​Run your Pages project locally

npx wrangler pages dev [directory] [ command]