💭

Cloudflare PagesにBasic認証をかけた話

2023/03/13に公開

今日は、みてねコールドクターでエンジニアマネージャーをしている遠藤です。
今日は、Cloudflare PagesにBasic認証をかける方法を紹介します。

背景

一部のサイトをCloudflare Pagesを使ってホスティングしています。
Cloudflare Pagesにはプレビュー機能という、ブランチごとに環境を作成する機能があります。
弊社では、開発の際にプレビュー環境を使って、レビュー&QAを行っています。
非常に便利なのですが、そのままだと、誰でもアクセスできてしまうので、認証をかける必要があります。
公式ではCloudflare Zero Trustを使う方法が紹介されていますが、社内事情により難しいので、Cloudflare Pages Functionを使ってBasic認証をかけました。

実装

functions/_middleware.js を作成します。内容は下記のとおりです。

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

const guardByBasicAuth = async ({ request, next, env }) => {
  if (env.BASIC_AUTH !== 'true') {
    return await next();
  }

  // Check header
  if (!request.headers.has('Authorization')) {
    return new Response(
      'You need to login.',
      {
        status: 401,
        headers: {
          // Prompts the user for credentials.
          'WWW-Authenticate': 'Basic realm="Input username and password"',
        },
      });
  }
  // Decode header value
  const [scheme, encoded] = request.headers.get('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(':');
  // eslint-disable-next-line no-control-regex
  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 !== env.BASIC_USERNAME || password !== env.BASIC_PASSWORD) {
    return new Response(
      'Invalid username or password.',
      {
        status: 401,
      },
    );
  }
  return await next();
};

export const onRequest = [errorHandler, guardByBasicAuth];

公式のworkersを使ったBasic認証の実装を参考にしました。
https://developers.cloudflare.com/workers/examples/basic-auth

以下のような修正を入れています。

  • 環境変数を使って、basic認証をするかどうかを切り替えるようにした。
  • ユーザー名とパスワードを環境変数から取得するようにした。

感想

Cloudflare Pages Functionを使って簡単にBasic認証をかけることができました。
ただ、この実装だと、productionでも毎回実行されるので、アクセス数次第では意外とコストがかかりそうなので、注意が必要です。
Cloudflare Zero Trustを制限かけるか、buildコマンドを使って、preview環境だけファイルが作成されるようにしたほうがいいかもしれません。

cloudflare pages functionをバリバリ使ってみたいエンジニアを募集中です

みてねコールドクターテックブログ

Discussion