4.9 KiB
4.9 KiB
Google OAuth2 sign-in
"Continue with Google" on /login and /register is wired through
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)
- User clicks Continue with Google →
GET /api/oauth2/googleredirects to Google's consent screen. - 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 ano_auth2_sessionsrow, sets its own private session cookie, and redirects toprotected_url. protected_url=GET /api/oauth2/protected(our bridge,controllers/oauth2.rs::complete). It mints ourauth_tokenJWT 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
- Go to https://console.cloud.google.com/ → create/select a project.
- APIs & Services → OAuth consent screen: configure it (External), add the
.../auth/userinfo.emailand.../auth/userinfo.profilescopes, and add your Google account as a test user while the app is in "Testing". - 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
- dev:
- 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.
# 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_KEYmust 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_URLhere and the Authorized redirect URI in the Google console must be byte-for-byte identical.
3. Run / test
nix develop -c cargo loco start # MUST be inside nix develop (OpenSSL link, see memory)
auto_migrate: true(dev) creates theo_auth2_sessionstable 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_KEYset to a fresh ≥64-byte key (not the dev sample).OAUTH_REDIRECT_URL/OAUTH_PROTECTED_URLuse the realhttps://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