oauth2
This commit is contained in:
104
docs/integrations/google-oauth.md
Normal file
104
docs/integrations/google-oauth.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# 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 <https://console.cloud.google.com/> → 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`
|
||||
Reference in New Issue
Block a user