홈 / Skills / 개발 생산성 / Google 로그인(웹) Sign In With Google
개발 생산성

Google 로그인(웹) 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)로 웹에 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 로그인 추가, 기존 연동 코드 검토.

한계
웹 전용입니다. 구형 Google Sign-In, 서비스 계정, Android/iOS 네이티브는 다루지 않습니다. 서버 예제는 Python이므로 Node, Go, Java에서는 직접 옮겨야 합니다.

참고 사항 및 위험

낮은 위험: Google 공식 Skills 저장소(Apache-2.0)의 지침 파일과 예제 코드만 들어 있습니다. 스크립트가 없고 자체 네트워크 접속과 파일 쓰기도 없습니다. Google OAuth 클라이언트 ID는 직접 준비해야 하며, 예제는 그 값이 들어갈 위치만 보여 줍니다. 예제는 출발점일 뿐 감사를 거친 코드가 아닙니다. FastAPI 예제는 nonce 쿠키가 있을 때만 nonce를 검증하므로, 자신의 코드에서는 이 검증을 필수로 바꾸세요. 인증 관련 코드는 배포 전에 반드시 직접 검토하세요.

SKILL.md 원본패키지에 파일 7개