0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloudflare Workers × Astro 環境でBasic認証を実装する

Posted at

はじめに

Basic認証付きの Astro を Cloudflare Workers で動かすのにハマったので、同じ実装をしたい方や、未来の自分のために記事に残します。

手順

1. Basic認証に用いる認証情報をシークレットに設定

wrangler secret put BASIC_AUTH_USER
wrangler secret put BASIC_AUTH_PASSWORD

2. Cloudflare 用のAstroアタプタをインストール

npm install @astrojs/cloudflare

3. astro.config.mjs に Cloudflare 用のアダプタを追加

astro.config.mjs
// @ts-check
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';

export default defineConfig({
  adapter: cloudflare({
    workerEntryPoint: {
      path: './src/worker.ts',
      namedExports: ['createExports'],
    },
    platformProxy: {
      enabled: true,
    },
  }),
});

4. src/worker.ts に Basic認証を実装

src/worker.ts
import { createExports as createAstroExports } from '@astrojs/cloudflare/entrypoints/server.js';

const BASIC_AUTH_REALM = 'Secure Area';
const UNAUTHORIZED_STATUS = 401;
const AUTHORIZATION_HEADER_KEY = 'Authorization';
const BASIC_PREFIX = 'Basic ';
const UNAUTHORIZED_BODY = '401 Unauthorized';

const createExpectedAuthorizationHeader = (
  username: string,
  password: string,
) => {
  const rawCredential = `${username}:${password}`;
  const base64Credential = btoa(rawCredential);
  return `${BASIC_PREFIX}${base64Credential}`;
};

const validateAuthorization = (
  authorizationHeader: string | null,
  expectedAuthorizationHeader: string,
) => authorizationHeader === expectedAuthorizationHeader;

const createUnauthorizedResponse = () =>
  new Response(UNAUTHORIZED_BODY, {
    status: UNAUTHORIZED_STATUS,
    headers: {
      // RFC 7235 に従い、ブラウザに再認証を促すチャレンジヘッダーを返却
      'WWW-Authenticate': `Basic realm="${BASIC_AUTH_REALM}"`,
      'Content-Type': 'text/plain; charset=utf-8',
      // 応答キャッシュを無効化して認証情報の漏洩を防止
      'Cache-Control': 'no-store',
    },
  });

const createExports = (manifest: Parameters<typeof createAstroExports>[0]) => {
  const astroExports = createAstroExports(manifest);
  const originalFetch = astroExports.default.fetch.bind(astroExports.default);

  // Astro のfetchハンドラにBasic認証を追加
  const fetch = async (request: Request, env: Env, ctx: ExecutionContext) => {
    const expectedAuthorizationHeader = createExpectedAuthorizationHeader(
      env.BASIC_AUTH_USER,
      env.BASIC_AUTH_PASSWORD,
    );
    const authorizationHeader = request.headers.get(AUTHORIZATION_HEADER_KEY);

    if (
      !validateAuthorization(authorizationHeader, expectedAuthorizationHeader)
    ) {
      return createUnauthorizedResponse();
    }

    // 認証成功時のみ Astro のSSRハンドラへ委譲
    // @ts-expect-error: Cloudflare Workers とAstro のRequest型に互換性がないため型キャストが必要
    return originalFetch(request, env, ctx);
  };

  // @ts-expect-error: Cloudflare Workers と Astro のfetch型定義に差異があるため型キャストが必要
  astroExports.default.fetch = fetch;

  return astroExports;
};

export { createExports };

5. Worker関連ファイルをアセットから除外

ビルドすると /dist/_worker.js フォルダができますが、 wrangler.json のアセット設定で directory に dist 、binding に ASSETS を指定している場合、これが含まれているとエラーになります。

/public/.assetsignore を作成し、 /_worker.js を除外しましょう。

public/.assetsignore
_worker.js

6. 各ページファイルで prerender を無効化

/distindex.html が入っていることで、Basic認証を行う前に index.html が表示されちゃいます。それを防ぐために各ページファイルで prerender を無効化します。

src/pages/index.astro
---
export const prerender = false;
---

<main>
  Hello world
</main>

今回の記事が誰かの役に立てれば幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?