アルアカ - Arcadia Academia

Arcadia Academiaは「エンジニアリングを楽しむ」を合言葉に日本のデジタル競争力を高めることをミッションとするテックコミュニティです。

Cloudflare Workers + Google OAuthで会員サイトを自作する方法を初心者向けに解説【ハンズオン】

Featured image of the post

「一般ユーザーがGoogleで登録・ログインできて、マイページや会員限定コンテンツがある」——そんな本格的な会員サイトを、サーバーを持たずに作れるのが Cloudflare Workers + Google OAuth の組み合わせです。

前記事の Cloudflare AccessでGoogle認証の限定公開サイトを作る は「許可した人だけ入れるゲート」でしたが、本記事は「誰でもGoogleで登録でき、会員ごとにデータを持てる」本格的な会員サイトを自作するアプローチです。基礎となるWorkersは Cloudflare Workers入門 をあわせてどうぞ。


[目次を開く]

作るものの全体像

Workers上で動く軽量フレームワーク Hono を使い、Google OAuthでログインして、会員限定ページを保護する仕組みを作ります。役割分担は次のとおりです。

要素 役割
Workers + Hono OAuthフロー・ルーティング・ページ生成
Google OAuth 認証(「Googleでログイン」)
D1 会員データ(ユーザーテーブル)
KV セッション保存
R2 会員限定のファイル配信(任意)

ログインの流れは一般的なOAuthと同じです。

ユーザー → /login → Googleの同意画面 → /callback
  → ユーザー情報を取得 → D1に保存 → セッション発行(KV+Cookie)
  → 以降はセッションで会員ページを表示

事前に準備するもの

  • Node.jsCloudflareアカウント(Workersが使える状態)
  • Google Cloud Console で作成するOAuthクライアント(client_id / client_secret)
  • wrangler(Workers CLI)

ステップ1:Google OAuthクライアントを作る

Google Cloud Console の「APIとサービス → 認証情報 → OAuthクライアントID」でウェブアプリケーションを作成します。承認済みのリダイレクトURIに、開発用と本番用を登録します。

# 開発
http://localhost:8787/callback
# 本番
https://あなたのドメイン/callback

取得した client_id / client_secret は後でWorkersのシークレットに登録します。

ステップ2:プロジェクトとバインディング

Workersプロジェクトを作り、HonoとOAuth補助ライブラリを導入します。

npm create cloudflare@latest my-members -- --framework=hono
cd my-members
npm i @hono/oauth-providers

wrangler.jsonc に、会員DB(D1)とセッション用KVをバインディングします。

{
  "name": "my-members",
  "main": "src/index.ts",
  "compatibility_date": "2026-06-01",
  "d1_databases": [
    { "binding": "DB", "database_name": "members", "database_id": "<作成したID>" }
  ],
  "kv_namespaces": [
    { "binding": "SESSIONS", "id": "<作成したID>" }
  ]
}

D1とKVは事前に作成しておきます。

npx wrangler d1 create members
npx wrangler kv namespace create SESSIONS

ステップ3:シークレットを登録する

client_id / client_secret はコードに直書きせず、Workersのシークレットに登録します。

npx wrangler secret put GOOGLE_CLIENT_ID
npx wrangler secret put GOOGLE_CLIENT_SECRET

ステップ4:Googleログインを実装する

Honoの Google OAuth ミドルウェアを使うと、コールバック処理の大半を任せられます。ログイン成功後に、ユーザーをD1に保存し、セッションを発行します。

import { Hono } from 'hono'
import { googleAuth } from '@hono/oauth-providers/google'
import { setCookie } from 'hono/cookie'

const app = new Hono()

app.use('/login', (c, next) =>
  googleAuth({
    client_id: c.env.GOOGLE_CLIENT_ID,
    client_secret: c.env.GOOGLE_CLIENT_SECRET,
    scope: ['openid', 'email', 'profile'],
    redirect_uri: new URL('/callback', c.req.url).toString(),
  })(c, next)
)

// コールバック(googleAuthがトークン交換を処理し、ユーザー情報を渡してくれる)
app.get('/callback', async (c) => {
  const user = c.get('user-google') // { email, name, ... }
  if (!user?.email) return c.text('認証に失敗しました', 401)

  // 会員をD1にupsert
  await c.env.DB.prepare(
    'INSERT INTO users (email, name) VALUES (?, ?) ' +
      'ON CONFLICT(email) DO UPDATE SET name = excluded.name'
  ).bind(user.email, user.name ?? '').run()

  // セッションをKVに保存し、Cookieを付与
  const sid = crypto.randomUUID()
  await c.env.SESSIONS.put(`sess:${sid}`, user.email, { expirationTtl: 60 * 60 * 24 * 7 })
  setCookie(c, 'sid', sid, { httpOnly: true, secure: true, sameSite: 'Lax', path: '/' })
  return c.redirect('/members')
})

export default app

事前にD1にusersテーブルを作っておきます。

CREATE TABLE IF NOT EXISTS users (
  email TEXT PRIMARY KEY,
  name  TEXT,
  created_at TEXT DEFAULT CURRENT_TIMESTAMP
);

ステップ5:会員限定ページを保護する

セッションCookieを検証するミドルウェアを作り、会員ページに適用します。未ログインなら /login へリダイレクトします。

import { getCookie } from 'hono/cookie'

const requireAuth = async (c, next) => {
  const sid = getCookie(c, 'sid')
  const email = sid ? await c.env.SESSIONS.get(`sess:${sid}`) : null
  if (!email) return c.redirect('/login')
  c.set('email', email)
  await next()
}

app.get('/members', requireAuth, (c) => {
  return c.html(`<h1>会員ページ</h1><p>ようこそ ${c.get('email')} さん</p>`)
})

これで、/members はログイン済みの会員だけが見られるページになりました。

ステップ6:デプロイと動作確認

# ローカル確認
npx wrangler dev
# 本番公開
npx wrangler deploy

本番公開後は、Google側のリダイレクトURIに本番ドメインの /callback が登録されているか必ず確認しましょう(ミスマッチが最頹のつまずきです)。

会員限定ファイルをR2で配信する(任意)

PDFや動画などの会員限定コンテンツを配るなら、R2と組み合わせます。リクエストを requireAuth で守ってからR2からファイルを返せば、ログイン済みの会員だけがダウンロードできます(R2の詳細は Cloudflare R2入門 を参照)。

app.get('/members/files/:key', requireAuth, async (c) => {
  const obj = await c.env.MY_BUCKET.get(c.req.param('key'))
  if (!obj) return c.notFound()
  return new Response(obj.body)
})

よくあるつまずき(FAQ)

Q. redirect_uri_mismatch エラーが出る

Google側に登録したリダイレクトURIと、アプリが送っているリダイレクトURIが完全一致していないのが原因です。末尾スラッシュや http/https、ポート番号まで揃えましょう。

Q. ローカルでは動くのに本番でエラーになる

シークレット(client_id/secret)が本番に未登録、または本番ドメインのリダイレクトURI未登録が多いです。wrangler secret list で確認しましょう。

Q. ログアウトは?

セッションをKVから削除し、Cookieを無効化します(SESSIONS.delete と Cookieの期限切れ)。

Q. Access(前記事)とどう使い分ける?

「社内・関係者だけの限定公開」ならAccessが手軽で安全です。「一般ユーザーが自由に登録し、会員ごとのデータや課金を持つ」なら本記事の自作アプローチです。

まとめ

Cloudflare Workers と Google OAuth を組み合わせれば、サーバーレスで本格的な会員サイトを自作できます。要点を振り返ります。

  • Hono + @hono/oauth-providers で Google OAuth を実装
  • 会員は D1、セッションは KV、会員限定ファイルは R2
  • client_id/secret は シークレットに、リダイレクトURIは完全一致させる

まずは Hello World のWorkerから始め、ログイン→セッション→会員ページ保護、と段階的に組んでいくのがおすすめです。

あわせて読みたい:

フロントエンド開発を学びたいですか?

React・Next.jsなど、モダンなフロントエンド開発をサポートします。

  • Reactの基礎から実践まで学びたい
  • Next.jsでアプリを作りたい
  • TypeScriptを使いこなしたい
まずは30分の無料相談

相談は完全無料・オンラインで気軽に

あなたを爆速で成長させるメンタリングプログラムはこちら

メンタープログラムバナー

学習・開発のお悩みは現役エンジニアに相談

メンタープログラムの詳細を見る

エンジニアの基礎学習ゲーム

プログラミング学習支援

無料相談はこちら