--- title: Google OAuth Flow tags: [flow, auth, oauth, google] related_models: ["[[User]]"] related_apis: ["POST /api/auth/google/signup", "POST /api/auth/google/signin"] --- # Google OAuth Flow > **Last updated:** 2026-05-29 — aligned with code (see [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md)) 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. - **Frontend** — `frontend/src/auth/services/google-oauth.ts`, consumed by `frontend/src/auth/view/jwt/jwt-sign-in-view.tsx` and `jwt-sign-up-view.tsx`. - **Backend** — `AuthController.googleSignUp` and `googleSignIn` in `backend/src/services/auth/authController.ts`, backed by `googleOAuthService.verifyGoogleToken()` in `backend/src/services/auth/googleOAuthService.ts`. - **MongoDB** — `User` 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 the email already exists, 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`, and the chosen `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, then looks up `User.findOne({ email: googleUser.email, status: "active" })` (`authController.ts:1194`). Note the **`status: "active"` filter**: the query only matches active accounts. If no active user matches, 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. > [!warning] No account merge > There is **no** account-merge step between a Telegram-only / email account and a Google account. The Google sign-in path simply looks up an **active** user by email and reuses that document if one exists; it does not reconcile, link, or merge distinct identities. There is **no** separate `googleId` field stored today, so matching is a one-way trust on `googleUser.email`. > [!warning] Soft-deleted accounts get a generic 404 on Google sign-in > Because the sign-in lookup filters by `status: "active"`, a user who registered via Google and was later **soft-deleted** (`status: "deleted"`) is invisible to the query. They receive the **same generic `404 USER_NOT_FOUND`** as a never-registered user — there is **no** distinct "account deleted" / "account disabled" error. ## Sequence diagram ```mermaid 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 alt Sign-up BE->>DB: User.findOne({ email }) else Sign-in BE->>DB: User.findOne({ email, status: "active" }) end alt Sign-up: email 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: no active user (missing or soft-deleted) 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:30` → `authController.googleSignUp` | | `POST` | `/api/auth/google/signin` | `authRoutes.ts:31` → `authController.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-signup`** → `user-${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 `null` → `401 INVALID_GOOGLE_TOKEN`. - **Email already exists during sign-up** → `409 USER_EXISTS`; frontend prompts to use sign-in instead. - **User does not exist during sign-in** → `404 USER_NOT_FOUND`; frontend redirects to sign-up. - **Soft-deleted user signs in via Google** → `404 USER_NOT_FOUND` (generic, indistinguishable from "never registered") because the lookup filters by `status: "active"`. - **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 - [[Authentication Flow]] — token issuance and storage are identical from step 9 onward. - [[Registration Flow]] — alternative path that requires email verification. - [[Referral Flow]] — works identically for Google-signup referrals. ## 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`