ホーム / Skills / 開発効率化 / Google ログイン(Web)Sign In With Google
開発効率化

Google ログイン(Web)Sign In With Google

GoogleApache-2.0Skill の言語: 英語
ライセンス確認済みスクリプトなし
デモ実行記録 · claude-sonnet-5-5 · 2026年10月
I'm adding Sign in with Google to a Next.js app that has a Node/Express backend. What architecture should I use and what must the server verify? Keep it concise.
Skill: sign-in-with-google-web

Architecture: Backend-mediated (BFF), with Express owning the session

You have a backend, so don't keep tokens in the browser. Pick one of two flows.

Option A: Next.js calls Express (recommended for a custom UI)

  1. Load GIS (https://accounts.google.com/gsi/client) in Next.js with <Script strategy="afterInteractive"> or an onload loader. Don't poll.
  2. Generate a random nonce server-side. Store it in the session, or in a short-lived HttpOnly cookie, and pass it to google.accounts.id.initialize({ client_id, nonce, callback }).
  3. In the callback, POST the credential (the ID token) to /api/auth/google with credentials: 'include'.
  4. Express verifies the token, creates a server session, and sets an HttpOnly; Secure; SameSite=Lax cookie.
  5. The ID token is never stored in localStorage or sessionStorage. The browser holds only the session cookie.

Option B: login_uri redirect
GIS POSTs the credential straight to Express (ux_mode: "redirect"). Express must check the double-submit CSRF value: the g_csrf_token cookie has to equal the g_csrf_token body field. Then it verifies the token, sets the cookie, and returns a 302. No JavaScript ever touches the token.

If Next.js and Express are on different origins, put them under one site or proxy /api through Next.js. That keeps the cookie first-party and makes SameSite=Lax work. Otherwise you need CORS with credentials, which is more fragile.

What the server must verify

Use OAuth2Client.verifyIdToken({ idToken, audience: CLIENT_ID }) from google-auth-library. Don't hand-roll JWT parsing. It checks the signature against Google's JWKS (https://www.googleapis.com/oauth2/v3/certs) and the standard claims. Make sure these hold:

Check Requirement
Signature Valid against Google's current public keys (the library caches and rotates them).
iss https://accounts.google.com or accounts.google.com.
aud Exactly your OAuth client ID. This is the most commonly missed check.
exp Not expired.
nonce Matches the value you issued for this session. It's one-time use, so delete it after the check (this blocks replay).
email_verified true before you trust or link on email.
sub Use this as the stable user key. Never use email as the primary identifier, because emails can change.
hd If you restrict to a Workspace domain, check the hd claim server-side. Client-side hd hints aren't enforcement.

For the Option B flow, also check the g_csrf_token cookie against the body field, as described above.

Session and hardening

  • Regenerate the session ID on login, to prevent session fixation.
  • Use CSRF protection on your own state-changing endpoints. SameSite=Lax helps but isn't enough on its own.
  • Send Cross-Origin-Opener-Policy: same-origin-allow-popups. Plain same-origin breaks the GIS popup silently.
  • Set a CSP that allows script-src https://accounts.google.com/gsi/client, frame-src https://accounts.google.com/gsi/, and connect-src https://accounts.google.com/gsi/.
  • On logout, call google.accounts.id.disableAutoSelect() and destroy the server session. Otherwise One Tap signs the user straight back in.
  • Don't use the legacy gapi.auth2 or gapi.signin2. They're retired. Use GIS only.
  • Don't trust any user info the client sends alongside the token. Take identity only from the verified payload.

Express sketch

const { OAuth2Client } = require('google-auth-library');
const client = new OAuth2Client(CLIENT_ID);

app.post('/api/auth/google', async (req, res) => {
  const ticket = await client.verifyIdToken({
    idToken: req.body.credential,
    audience: CLIENT_ID,
  });
  const p = ticket.getPayload();

  if (!p.email_verified) return res.sendStatus(401);
  if (p.nonce !== req.session.nonce) return res.sendStatus(401);
  delete req.session.nonce;
  // if (p.hd !== 'yourdomain.com') return res.sendStatus(403);

  const user = await upsertUserBySub(p.sub, p.email);
  req.session.regenerate(() => {
    req.session.userId = user.id;
    res.sendStatus(204);
  });
});

This is a sketch. Wrap verifyIdToken in try/catch and return 401 on failure. Also make sure regenerate doesn't lose the nonce check order.

I can wire this into your project if you'd like. It would help to know whether Next.js and Express share an origin.

Pure-instruction skill, so the trial gave the model only the Skill and Read tools. The generated Express sketch was not run or tested; the model itself says to wrap verification in try/catch and check the nonce handling order. Review all authentication code before using it.

できること
Google Identity Services(GIS)による Web の Google ログイン実装をモデルに案内します。トークンをブラウザの JavaScript に渡さないバックエンド経由の構成を優先し、廃止された gapi.auth2 を使わないよう警告します。

仕組み

  1. 構成の選択:Backend For Frontend(推奨)、login_uri へのフォーム POST リダイレクト(推奨)、静的サイトやオフライン PWA 向けのクライアントのみの代替案。
  2. 必須のブラウザ側設定:CSP、Cross-Origin-Opener-Policy: same-origin-allow-popups、FedCM と One Tap の挙動、サインアウト時の disableAutoSelect()、iframe での identity-credentials-get 権限。
  3. 参考実装:FastAPI での ID トークン検証(署名、audience、nonce、Workspace の hd)、リダイレクトモードのダブルサブミット CSRF、React のメモリ保持パターン、暗号化 IndexedDB パターン、intermediate iframe。

向いている場面
React、Next.js、Angular、素の HTML サイトへの Google ログイン追加、既存実装のレビュー。

制限
Web のみが対象です。旧 Google Sign-In、サービスアカウント、Android/iOS ネイティブは対象外です。サーバー側のサンプルは Python なので、Node・Go・Java では読み替えが必要です。

補足とリスク

低リスク:Google 公式の Skills リポジトリ(Apache-2.0)にある指示ファイルとサンプルコードのみで、スクリプトなし、自身の通信なし、ファイル書き込みなしです。Google OAuth のクライアント ID はご自身で用意する必要があり、サンプルはその置き場所を示すだけです。サンプルは出発点であり、監査済みのコードではありません。FastAPI の例では nonce cookie がある場合にだけ nonce を検証するため、自分のコードでは必須の検証にしてください。認証まわりのコードは公開前に必ず自分で確認してください。

SKILL.md 原文パッケージ内 7 ファイル