# Google OAuth2 sign-in "Continue with Google" on `/login` and `/register` is wired through [`loco-oauth2`](https://github.com/yinho999/loco-oauth2). The code is complete and compiles; this doc is the checklist to make the live flow work. Until the credentials below are set, the button reaches Google and fails at the consent screen — the rest of auth (password login, registration, verification) is unaffected. ## How the flow works (for context) 1. User clicks **Continue with Google** → `GET /api/oauth2/google` redirects to Google's consent screen. 2. Google redirects back to `GET /api/oauth2/google/callback/cookie`. loco-oauth2 exchanges the code, fetches the profile, upserts the user (`OAuth2UserTrait::upsert_with_oauth`), stores an `o_auth2_sessions` row, sets its own private session cookie, and redirects to `protected_url`. 3. `protected_url` = `GET /api/oauth2/protected` (our bridge, `controllers/oauth2.rs::complete`). It mints **our** `auth_token` JWT cookie and redirects: admins (email == `ADMIN_EMAIL`) → `/admin/dashboard`, everyone else → `/`. From there the user is a normal logged-in user (same JWT cookie as a password login; the Casbin layer and guards treat them identically). ## 1. Create Google OAuth credentials 1. Go to → create/select a project. 2. **APIs & Services → OAuth consent screen**: configure it (External), add the `.../auth/userinfo.email` and `.../auth/userinfo.profile` scopes, and add your Google account as a **test user** while the app is in "Testing". 3. **APIs & Services → Credentials → Create Credentials → OAuth client ID**: - Application type: **Web application**. - **Authorized redirect URIs** — add exactly (must match the config's `redirect_url`, no trailing slash): - dev: `http://localhost:5150/api/oauth2/google/callback/cookie` - prod: `https://YOUR_DOMAIN/api/oauth2/google/callback/cookie` 4. Copy the generated **Client ID** and **Client secret**. ## 2. Set environment variables (`.env`) Read by `config/development.yaml` → `initializers.oauth2` (and the prod equivalent). dotenvy loads `.env` on boot. ```bash # Required OAUTH_CLIENT_ID=xxxxxxxx.apps.googleusercontent.com OAUTH_CLIENT_SECRET=xxxxxxxx # Required in PRODUCTION (dev has working defaults) OAUTH_PRIVATE_KEY="comma,separated,bytes >= 64 long" # key for loco-oauth2's private cookie jar OAUTH_REDIRECT_URL=https://YOUR_DOMAIN/api/oauth2/google/callback/cookie OAUTH_PROTECTED_URL=https://YOUR_DOMAIN/api/oauth2/protected ``` Notes: - **dev** ships defaults for everything except `OAUTH_CLIENT_ID` / `OAUTH_CLIENT_SECRET`, so locally you only need those two. - `OAUTH_PRIVATE_KEY` must be ≥ 64 bytes (the dev default is a sample key — do **not** reuse it in production). Generate a fresh one, e.g. `python3 -c "import os;print(','.join(str(b) for b in os.urandom(64)))"`. - `OAUTH_REDIRECT_URL` here and the Authorized redirect URI in the Google console must be byte-for-byte identical. ## 3. Run / test ```bash nix develop -c cargo loco start # MUST be inside nix develop (OpenSSL link, see memory) ``` - `auto_migrate: true` (dev) creates the `o_auth2_sessions` table on boot. - Open `http://localhost:5150/login` → **Continue with Google** → consent → you should land back on `/` logged in (cart/nav reflect the session). ## 4. Production checklist - [ ] Separate OAuth client (or at least the prod redirect URI) in Google. - [ ] OAuth consent screen **published** (not just "Testing"), or real users get blocked. - [ ] `OAUTH_PRIVATE_KEY` set to a fresh ≥64-byte key (not the dev sample). - [ ] `OAUTH_REDIRECT_URL` / `OAUTH_PROTECTED_URL` use the real `https://` origin. - [ ] `server.host` / public origin correct so cookies + redirects resolve. ## Troubleshooting | Symptom | Cause / fix | |---|---| | `redirect_uri_mismatch` at Google | Authorized redirect URI ≠ `OAUTH_REDIRECT_URL`. Make them identical (scheme, host, port, path, no trailing slash). | | 403 / "access blocked: app not verified" | Add your account as a test user, or publish the consent screen. | | `openssl-sys ... Could not find directory` at build | You ran `cargo` outside the dev shell. Use `nix develop -c cargo ...`. | | Callback 500 / "could not create oauth2 store" | `initializers.oauth2` missing/invalid, or `OAUTH_PRIVATE_KEY` < 64 bytes. | | Logged into Google but not into the app | The bridge (`/api/oauth2/protected`) didn't run — check `protected_url` (`OAUTH_PROTECTED_URL`) points at it. | ## Where things live - Config: `config/development.yaml` / `config/production.yaml` → `initializers.oauth2` - Client store + session initializers: `src/initializers/oauth2.rs`, `src/initializers/oauth2_session.rs` - Routes + bridge handler: `src/controllers/oauth2.rs` - User upsert (random password per advisory LOC-2025-04): `src/models/users.rs` (`OAuth2UserTrait`) - Session table: `src/models/o_auth2_sessions.rs` + `migration/.../m20260618_000001_o_auth2_sessions.rs`