✍ 本記事は AI(ChatGPT)を活用して下書きを作成し、筆者が検証・編集のうえ公開しています。
TL;DR
- Google OAuth + PKCE を Nuxt3 CSR で使うと
client_secret
問題に詰まる - ミニ BFF (Express / Spring でも可) に
/auth/exchange
を置けば解決 - フルサンプルは GitHub → nuxt3-google-oauth-pkce-sample
背景
Google の OAuth PKCE フローは Web アプリ登録だと client_secret が必須。CSR 環境で動かすにはBFFでトークン交換を代行させるのが現実的な解法です。この記事では Nuxt 3 CSR + Express マイクロサービス構成のサンプルをステップごとに紹介します。
あるプロジェクトで Nuxt 3 CSR アプリとバックエンド API を組み合わせる際、Google の PKCE フローで client_secret が必須となり動作しない問題が発生しました。本記事では、それを解決するための Express マイクロサービス(BFF)的な構成を解説します。
目次
-
背景 ― なぜシークレット問題にハマるのか
-
SPA(CSR)構成 で取り得る 4 つのアーキテクチャ
-
実装ステップ
-
Google Cloud 側の設定
-
Express マイクロサービス (コード → トークン交換)
-
Nuxt 3 CSR クライアント
-
付録 A. デプロイ & サーバーレス化 Tips
-
まとめ・今後の改善ポイント
1. 背景 ― Google PKCE で client_secret が必須になる理由
-
RFC 7636 (PKCE) は本来 Public Client 向け → シークレット不要のはず
-
Google は Web アプリ 登録時に「クライアント認証は必須」という実装ポリシー
-
CSR 単体ではトークンエンドポイントを呼べず、BFF などでシークレットを隠蔽する必要がある
2. フロント単体 (CSR) で取り得る 4 つのアーキテクチャ
パターン | 概要 | Pros | Cons |
---|---|---|---|
A. CSR + マイクロサービス (今回) | Express/Lambda がトークン交換 | Google 対応可静的ホスティングと親和性◎ | サーバー管理が必要 |
B. SSR + BFF | Nuxt SSR 内でシークレット保持 | 同一オリジンで完結 | SSR 運用コスト |
C. Public Client プロバイダ | Auth0 / Azure AD など | フロントだけで完結 | Google 利用不可 |
D. Implicit Flow | レガシー方式 | サーバー不要 | セキュリティ弱い |
本記事では A: CSR + ミニ Express (Spring でも可) を実装。
2.1 他の実装パス
-
Spring Bootに
spring-boot-starter-oauth2-client
を組み込む
→ バックエンドが Java の場合はコード→トークン交換を Spring Security が代行できる
特定プロバイダのライブラリに依存するため今回は不採用
3. 実装ステップ
3.1 Google Cloud 設定
-
OAuth 同意画面: アプリ名・ドメイン登録
-
認証情報 → OAuth 2.0 クライアント ID
- アプリケーションの種類: ウェブアプリケーション
- 名前: 任意
- 承認済みの JavaScript 生成元: http://localhost:3000
- 承認済みのリダイレクトURI: http://localhost:3000/auth/callback
-
client_id と client_secret を控える
3.2 Express BFF (server/)
- 依存:
express cors dotenv node-fetch qs
- 重要ポイント
-
express.urlencoded()
で form-urlencoded をパース -
/auth/exchange
でclient_secret
を付けてトークン取得
-
router.post('/', async (req, res, next) => {
const { code, code_verifier } = req.body
const conf = await fetch(process.env.OIDC_WELL_KNOWN).then(r=>r.json())
const token = await fetch(conf.token_endpoint, {
method: 'POST',
headers: {'Content-Type':'application/x-www-form-urlencoded'},
body: qs.stringify({
grant_type:'authorization_code',
client_id:process.env.OIDC_CLIENT_ID,
client_secret:process.env.OIDC_CLIENT_SECRET,
redirect_uri:process.env.OIDC_REDIRECT_URI,
code, code_verifier
})
}).then(r=>r.json())
res.json(token)
})
3.3 Nuxt 3 CSR (client/)
-
依存:
@pinia/nuxt oidc-client-ts
-
plugins/oidc.client.js
で token_endpoint をapiBase + /auth/exchange
に上書き -
handleCallback()
でsigninCallback()
→ トークンとプロフィールをストアへ
export const handleCallback = async () => {
const user = await mgr.signinCallback()
store.setTokens({ access_token: user.access_token })
store.setUser(user.profile)
await navigateTo('/')
}
3.4 動作確認
# server
npm run dev
# client
npm run dev
付録 A. デプロイ & サーバーレス化 Tips
レイヤー | 例 | ポイント |
---|---|---|
フロント | Netlify / Vercel / Cloudflare Pages |
nuxt generate 出力で即デプロイ |
BFF | Vercel Functions / Cloudflare Workers / AWS Lambda |
.env シークレットはプラットフォーム管理 |
4. まとめ・今後の改善
-
リフレッシュトークン運用:httpOnly Cookie + Token Rotation
-
セキュリティ強化:
express-rate-limit
& Helmet & CORS 制限 -
別 ID プロバイダ:Microsoft identity platform 等ならフロント完結も可能
-
SSR/BFF 移行:必要に応じて Nuxt Nitro に統合するとオリジンを一本化できる
5. ソースコード
GitHub にて今回紹介した構成の全コードを公開しています:
nuxt3-google-oauth-pkce-sample - GitHub リポジトリ
静的ホスティング × Google OAuth の“微妙な壁”にハマった方の助けになれば幸いです!