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に型付け
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]