はじめに
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 を無効化
/dist に index.html が入っていることで、Basic認証を行う前に index.html が表示されちゃいます。それを防ぐために各ページファイルで prerender を無効化します。
src/pages/index.astro
---
export const prerender = false;
---
<main>
Hello world
</main>
今回の記事が誰かの役に立てれば幸いです。