🛡️

Cloudflare Accessを使って、Pagesにアップしたサイトを保護する話

2024/12/19に公開

これは、信州大学 kstm Advent Calendar 2024 4日目の記事です。
https://qiita.com/advent-calendar/2024/kstm

はじめに

当記事は、Cloudflare PagesにCloudflareの提供するCloudflare Accessという簡単で安全なユーザーアクセスを提供するサービスを利用することで、デプロイするページに対してアクセス制御を行うことを目的としています。
今回、デプロイするサイトを作成するためにNext.jsを利用しました。

Cloudflare Pagesとは

Cloudflare Pagesとは、Cloudflareの提供するWebサイトのホスティングサービスです。
類似のサービスとしてGithub Pagesなどがありますが、データ転送量制限の制限などがあるため、今回Cloudflare Pagesを利用しました。また、Cloudflare Pagesの場合Server Side Renderingなど一部のNext.jsの機能を利用することができます。

Next.jsをCloudflare Pages上に作成する

Cloudflare PagesにデプロイするNext.jsのページを作成するにはcreate-cloudflare CLI(C3)を利用します。

npm create cloudflare@latest -- my-next-app --framework=next

また、cloudflare pages上でNext.jsを利用する場合edge runtimeを利用する必要があります。

export const runtime = "edge";

詳しい手段については、こちらを参照してください。

APIルートを作成する

また、APIルートの作成にはHonoを利用しました。

src/app/api/[[...route]]/route.ts
import { Hono } from "hono";
import { handle } from "hono/vercel";
export const runtime = "edge";
//DB(D1)にアクセスするため記述しています。特に触らない場合必要ありません。
type Bindings = {
  DB: D1Database;
};
// basePath は API ルートのベースパスを指定します
// 以降、新たに生やす API ルートはこのパスを基準に追加されます
const app = new Hono<{ Bindings: Bindings }>().basePath("/api");

app.get('/', (c) => c.text('Hono!'))

export const GET = handle(app);
export const POST = handle(app);

Next.js上において、apiルートをhonoで記述する場合VercelのAdapterを利用することで実現できます。
今回のコード例においては、最後の2文がこれに当たります。

デプロイの方法

Gitレポジトリの連携や、CLIからのデプロイなどの方法があるためそれぞれの用途に応じて設定します。
今回はGitレポジトリと連携することで、GitHubにpushした際に自動的にデプロイする形としました、

Cloudflare Accessを利用したアクセス制御

Pagesにデプロイすることができたら、次にCloudflare Accessを利用したアクセス制御を行います。
こちらを利用する理由として、まだ作成途中のアプリケーションなどに対して開発者のみアクセスすることができるようにしたりすることにより、インターネット上にプレビューを公開しながらもアクセスを許可されたものだけが閲覧することができます。

Cloudflare PagesでAccessを有効にするには、Pagesのプロジェクトに移動し、Settings(設定)タブに移動したのち、General(一般) > Access policy(Access ポリシー)を有効にします。
有効にしたのち、manage(管理)をクリックすることで、CloudflareのZero Trustのページに移動するため、こちらでAccessの設定を行います。
Accessのタブにおいて、プロジェクト名 - Cloudflare Pagesというapplicationが作成されているのが確認できると思います。

Githubとの連携の設定

今回はGithub organizationのメンバー全員に対してアクセス権を付与することを想定します。
Settings > Authentication > Login methods > Add newに移動し、App IDの設定などを行います。
Github integration on Login method
これにより、ログインの方法としてGitHubを利用することができるようになりました。

他者のアクセスを許可する

Access > Applicationをクリックし、Configurateを押すことでPolicyタブに移動すると思います。

まず、初期設定ではプレビュー用の*.example.comのみが保護されているため、example.comについても保護するため、domainを追加します、カスタムドメインを利用している場合もこちらに追加してください。

ドメインの設定画面

次に、Add policyをおし、Policyの設定を行います。

Policy name Actions Session duration
任意の名前 Allow 必要に応じて(できるだけ短いほうが望ましい)

Policy
Add Policyをおし、Configure rulesにおいて、SelectorにおいてGithub Organizationを選択し、GitHub organization nameを入力します。
Configurate rules
その後、Add policyを押して設定を保存します。

Authonicationタブ
そして、AuthonicationタブからGithubをクリックすることで、当アプリケーションにおいて、Githubによる認証を有効にすることができました。

React Server Component(RSC)からのAPIアクセスについて

従来ではデータベースへのアクセスなどについては隠ぺいのため、/api配下においてアクセスなどを行ってきました。
しかし、RSCの登場により、DBのアクセスについてもデータを取得したのち、コンポーネントを作成してからクライアントに返却するため、コンポーネントと同一コードにおいてアクセスすることができるようになりました。また、/apiにserver sideからのアクセスについてはパフォーマンスの低下などのデメリットがあるため、必要がなければRSC内で直接データを取得することが望まれます。
しかし、RSC以前のNext.jsからの以降の最中やDBのアクセスロジックについて/apiにまとめておきたいなどの場合、RSCから自分自身にfetchを行うことがあるかもしれません。

const headersData = headers();
const host = headersData.get("host");
if (!host) {
  throw new Error("Host header is missing");
}
const protocol =
    headersData.get("x-forwarded-proto") ??
    (host.startsWith("localhost") ? "http" : "https");
const apiBase = `${protocol}://${host}`;

Next.jsのRSCにおいて自分自身にアクセスするには絶対パスのurlが必要になるため上記のようなコードでアクセスを行うことになります。
しかし、上記コードからのアクセスについては先ほど実装したCloudflare Accessよって制限されているため、アクセスできません。
そこで、Service Authという機能を利用することでアクセスできるようになります。

Service Authについて、Access > Service Authに移動し、Create Service Tokenで新規Tokenを作成します。

そのまま進み、CF_ACCESS_CLIENT_IDとCF_ACCESS_CLIENT_SECRETを取得します。
そののち下記コードのようにHeaderに追加し、fetchの際にheaderを付与することで、RSCからもapi routeにアクセスすることができるようになりました。

const appendHeaders = new Headers();
  appendHeaders.append(
    "CF-Access-Client-Id",
    process.env.CF_ACCESS_CLIENT_ID as string,
  );
  appendHeaders.append(
    "CF-Access-Client-Secret",
    process.env.CF_ACCESS_CLIENT_SECRET as string,
  );

参考文献
https://developers.cloudflare.com/pages/framework-guides/nextjs/ssr/
https://opennext.js.org/cloudflare/get-started
https://zenn.dev/y640/articles/20240517-cloudflare-workers-with-cloudflare-access
https://nextjs-faq.com/fetch-api-in-rsc

Discussion