Files
nick-doc/04 - Flows/Google OAuth Flow.md

7.7 KiB

title, tags, related_models, related_apis
title tags related_models related_apis
Google OAuth Flow
flow
auth
oauth
google
User
POST /api/auth/google/signup
POST /api/auth/google/signin

Google OAuth Flow

Google sign-in/up integration using Google Identity Services (accounts.google.com/gsi/client). The flow short-circuits email verification because Google accounts are pre-verified.

Actors

  • User with a Google account.
  • Google Identity Services (GSI) — JS SDK loaded on demand by the frontend service.
  • Frontendfrontend/src/auth/services/google-oauth.ts, consumed by frontend/src/auth/view/jwt/jwt-sign-in-view.tsx and jwt-sign-up-view.tsx.
  • BackendAuthController.googleSignUp and googleSignIn in backend/src/services/auth/authController.ts, backed by googleOAuthService.verifyGoogleToken() in backend/src/services/auth/googleOAuthService.ts.
  • MongoDBUser collection (account linking by email).

Preconditions

  • NEXT_PUBLIC_GOOGLE_CLIENT_ID is set on the frontend (frontend/src/auth/services/google-oauth.ts line 2 — there is a hard-coded fallback for the dev project ID).
  • Same client ID is whitelisted on the backend googleOAuthService.
  • The current origin is registered under "Authorized JavaScript origins" in Google Cloud Console (see frontend/GOOGLE_OAUTH_SETUP.md).

Step-by-step narrative

Sign-up

  1. User clicks the Google icon on /auth/jwt/sign-up. The form is configured with the chosen role (buyer/seller) and an optional referral code.
  2. The frontend lazy-loads https://accounts.google.com/gsi/client if it is not yet on window.google.
  3. google.accounts.oauth2.initTokenClient({ client_id, scope: 'openid email profile' }) is initialised, and .requestAccessToken() opens the Google popup.
  4. On success the popup returns an ID token (a Google-signed JWT containing email, email_verified, name, given_name, family_name, picture, sub).
  5. Frontend calls signUpWithGoogle({ googleToken, role, referralCode }) (frontend/src/auth/context/jwt/action.ts:281-304), which POSTs POST /api/auth/google/signup.
  6. Backend authController.googleSignUp (:781-872) calls googleOAuthService.verifyGoogleToken(googleToken). The verifier uses google-auth-library to validate the JWT signature, expiry, audience (client_id), and issuer.
  7. Duplicate check: User.findOne({ email: googleUser.email }) — if found returns 409 USER_EXISTS so the user can use sign-in instead.
  8. New user creation with password omitted, isEmailVerified: true, status: "active", profile.avatar = googleUser.picture, role from the request.
  9. Referral attribution (authController.ts:817-838): same logic as the email path — increment referrer.referralStats.totalReferrals, emit referral-signup on user-${referrer._id}.
  10. Generate access + refresh tokens, push refresh into user.refreshTokens[], respond with { user, tokens }.
  11. Frontend stores tokens in localStorage and redirects to the dashboard.

Sign-in

  1. User clicks the Google icon on /auth/jwt/sign-in.
  2. Same GSI flow as sign-up — Google returns an ID token.
  3. Frontend calls signInWithGoogle(googleToken)POST /api/auth/google/signin.
  4. Backend verifies the token, looks up User.findOne({ email: googleUser.email }). If no user, returns 404 USER_NOT_FOUND ("please sign up first"). The frontend surfaces a localized prompt.
  5. On hit: existingUser.lastLoginAt = now; if profile.avatar is empty and Google has a picture, it is back-filled (authController.ts:905-907).
  6. Tokens issued and returned identically to email login.

[!tip] Account linking is implicit by email A user who originally signed up via email + password can sign in with Google as long as the email matches — no extra "link account" step. The backend simply reuses the existing user document. There is no separate googleId field stored today, so this is a one-way trust on googleUser.email.

Sequence diagram

sequenceDiagram
    autonumber
    actor U as User
    participant FE as Frontend
    participant G as Google GSI
    participant BE as Backend
    participant GA as google-auth-library
    participant DB as MongoDB

    U->>FE: Click Google icon
    FE->>G: load gsi/client, initTokenClient(client_id)
    FE->>G: requestAccessToken()
    G-->>U: Popup → consent
    U-->>G: Approve
    G-->>FE: ID token (signed JWT)
    alt Sign-up
        FE->>BE: POST /api/auth/google/signup { googleToken, role, referralCode }
    else Sign-in
        FE->>BE: POST /api/auth/google/signin { googleToken }
    end
    BE->>GA: verifyGoogleToken(googleToken)
    GA-->>BE: { email, name, picture, ... } or null
    BE->>DB: User.findOne({ email })
    alt Sign-up: user exists
        BE-->>FE: 409 USER_EXISTS
    else Sign-up: new
        BE->>DB: User.create({ email, role, isEmailVerified:true, profile.avatar })
        opt referral
            BE->>DB: increment referrer.referralStats
        end
    else Sign-in: user missing
        BE-->>FE: 404 USER_NOT_FOUND
    else Sign-in: ok
        BE->>DB: set user.lastLoginAt = now
        BE->>DB: back-fill avatar if blank
    end
    BE->>BE: generate access and refresh tokens
    BE->>BE: push refresh token
    BE-->>FE: 200 { user, tokens }
    FE->>FE: localStorage.setItem(accessToken/refreshToken)
    FE-->>U: Redirect /dashboard/{role}

API calls

Method Endpoint Source
POST /api/auth/google/signup authRoutes.ts:30authController.googleSignUp
POST /api/auth/google/signin authRoutes.ts:31authController.googleSignIn

Database writes

  • users collection: on sign-up, full insert (no password). On sign-in, only lastLoginAt, possibly profile.avatar, and a new refresh token appended.

Socket events emitted

  • referral-signupuser-${referrerId} when sign-up includes a valid referralCode.

Side effects

  • No email is sent (Google handles trust). No TempVerification is created.
  • The avatar URL is stored from Google's CDN; consider proxying or rehosting if Google's privacy rules change for googleusercontent.com.

Error / edge cases

  • Invalid Google token (bad signature, wrong audience, expired) → googleOAuthService returns null401 INVALID_GOOGLE_TOKEN.
  • User already exists during sign-up409; frontend prompts to use sign-in instead.
  • User missing during sign-in404; frontend redirects to sign-up.
  • Popup blocker → GSI throws a client-side error caught in the view and surfaced as a toast.
  • Network failure to accounts.google.com → GSI rejects; frontend retries on next click.
  • email_verified === false on the Google token → currently not enforced; the backend trusts any successful Google response. For an extra-strict mode, gate on googleUser.email_verified === true in googleOAuthService.

[!warning] Single backup file frontend/src/auth/services/google-oauth.ts.backup is checked in. Delete or convert to a documentation note — it leaks a hard-coded client ID that should only live in .env.*.

Linked flows

Source files

  • Frontend: frontend/src/auth/services/google-oauth.ts
  • Frontend: frontend/src/auth/context/jwt/action.ts:281-331
  • Frontend: frontend/src/auth/view/jwt/jwt-sign-up-view.tsx, jwt-sign-in-view.tsx
  • Frontend: frontend/GOOGLE_OAUTH_SETUP.md
  • Backend: backend/src/services/auth/authController.ts:781-941
  • Backend: backend/src/services/auth/googleOAuthService.ts
  • Backend: backend/src/services/auth/authRoutes.ts:30-31