Nuxt 3 + Google OAuth (PKCE) Minimal Setup Guide for Pure SPA (CSR)
ruuma

ruuma @ruuma_fb53ad7177cba32d962

Location:
Japan
Joined:
Jun 21, 2025

Nuxt 3 + Google OAuth (PKCE) Minimal Setup Guide for Pure SPA (CSR)

Publish Date: Jun 25
0 0

✍ Originally published in Japanese on Qiita, this article was translated into English using ChatGPT and then reviewed & edited by the author.

TL;DR


Background

Google’s PKCE flow requires client_secret for Web App registrations.

Since a CSR-only app can’t keep secrets, the realistic solution is to proxy the token exchange through a BFF.

This article walks through a Nuxt 3 CSR + Express micro-service architecture that unblocks the flow.


Table of Contents

  1. Why the client-secret trap exists
  2. Four architecture options for pure-front setups
  3. Step-by-step implementation
  4. Google Cloud configuration
  5. Express micro-service (code → token exchange)
  6. Nuxt 3 CSR client
  7. Appendix A: Deploy & serverless tips
  8. Conclusions & next steps

1. Why Google PKCE still wants a client secret

  • RFC 7636 (PKCE) targets public clients → secrets should be unnecessary.
  • Google’s policy: Web-App registration always demands client authentication.
  • A CSR app therefore cannot call the token endpoint directly; a BFF must shield the secret.

2. Four Architecture Options for Pure CSR

Pattern Outline Pros Cons
A. CSR + Micro-Service (this post) Express/Lambda handles token exchange Works with Google, perfect for static hosting Need to run/maintain a micro-service
B. SSR + BFF Keep secret inside Nuxt SSR Same origin, no CORS hassle SSR hosting & ops cost
C. Public-Client Provider Auth0, Azure AD, etc. Front-end only Google not supported
D. Implicit Flow Legacy grant No server Weaker security

We implement A: CSR with a tiny Express (Spring works too).

2.1 Other approaches

  • Spring Boot + spring-boot-starter-oauth2-client Spring Security can perform the exchange for Java back-ends, but introduces provider-specific dependencies—skipped here.

3. Implementation Steps

3.1 Configure Google Cloud

  1. OAuth consent screen – set app name & domain.
  2. Credentials → OAuth 2.0 Client ID
    • Application type: Web application
    • Authorized JS origins: http://localhost:3000
    • Redirect URI: http://localhost:3000/auth/callback
  3. Copy client_id and client_secret.

3.2 Express BFF (server/)

Dependencies: express cors dotenv node-fetch qs

router.post('/', async (req, res) => {
  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);
});
Enter fullscreen mode Exit fullscreen mode

3.3 Nuxt 3 CSR (client/)

  • Dependencies: @pinia/nuxt, oidc-client-ts
  • Override token_endpoint to your BFF: apiBase + '/auth/exchange'.
  • Handle the callback:
export const handleCallback = async () => {
  const user = await mgr.signinCallback();
  store.setTokens({ access_token: user.access_token });
  store.setUser(user.profile);
  await navigateTo('/');
};
Enter fullscreen mode Exit fullscreen mode

3.4 Run locally

# server
npm run dev

# client
npm run dev
Enter fullscreen mode Exit fullscreen mode

Appendix A. Deploy & Serverless Tips

Layer Example platform Key points
Front-end Netlify / Vercel / Cloudflare Pages Deploy nuxt generate output as-is
BFF Vercel Functions / Cloudflare Workers / AWS Lambda Store .env secrets in platform settings

4. Conclusions & Next Steps

  • Refresh-token handling: httpOnly cookies + rotation
  • Security hardening: express-rate-limit, Helmet, strict CORS
  • Other IdPs: Microsoft identity platform lets you stay front-end-only
  • SSR/BFF migration: integrate with Nuxt Nitro if you ever need a unified origin

5. Source Code

All code shown here:
https://github.com/RuumaLilja/nuxt3-google-oauth-pkce-sample

Hope this saves you from falling into Google OAuth’s “client_secret” pit!

Comments 0 total

    Add comment