6.4 KiB
6.4 KiB
bincio-auth → OIDC Identity Provider
Goal: make bincio-auth a proper OpenID Connect (OIDC) Identity Provider so every bincio service (activity, wiki, Gitea, mobile apps, future tools) delegates auth to it. One account, one password, self-service email reset, no manual user sync per service.
Status: planning
Architecture
bincio-auth becomes the IdP. Every other service is a client.
User → Gitea / activity / wiki / mobile
→ "not logged in → go to bincio.org/oauth2/authorize"
→ user logs in once at bincio.org
→ redirected back with short-lived auth code
→ client POSTs code → bincio-auth returns id_token (RS256 JWT)
→ client verifies token locally via JWKS (no shared secret needed)
Signing: RS256 asymmetric key pair.
Private key on bincio-auth only. Public key published at /.well-known/jwks.json.
Clients verify tokens without any shared secret — truly decoupled.
New endpoints (all additive — nothing existing changes)
| Endpoint | Purpose |
|---|---|
GET /.well-known/openid-configuration |
OIDC discovery — Gitea and clients read this |
GET /.well-known/jwks.json |
Public RSA key for token verification |
GET /oauth2/authorize |
Login + consent page, issues short-lived auth code |
POST /oauth2/token |
Exchanges auth code for id_token + access_token |
GET /oauth2/userinfo |
Returns profile from access_token |
New DB tables
-- Registered OAuth2 clients (Gitea, bincio-activity, mobile app, ...)
CREATE TABLE oauth2_clients (
client_id TEXT PRIMARY KEY,
client_secret TEXT, -- NULL for public PKCE clients (mobile)
name TEXT NOT NULL,
redirect_uris TEXT NOT NULL, -- JSON array of allowed redirect URIs
scopes TEXT NOT NULL DEFAULT 'openid profile',
created_at INTEGER NOT NULL
);
-- Short-lived single-use authorization codes (expire in 5 min)
CREATE TABLE oauth2_codes (
code TEXT PRIMARY KEY,
client_id TEXT NOT NULL,
handle TEXT NOT NULL,
redirect_uri TEXT NOT NULL,
scope TEXT NOT NULL,
code_challenge TEXT, -- PKCE (mobile clients)
code_challenge_method TEXT, -- 'S256'
created_at INTEGER NOT NULL,
expires_at INTEGER NOT NULL,
used_at INTEGER
);
Migration phases
Phase 0 — Preparation [ ]
- Generate RSA key pair, store private key at
/etc/bincio/oidc_private.pem - Choose and configure SMTP provider (Postmark or Brevo free tier — VPS IPs often blocked by Gmail/Outlook)
- Register initial OAuth2 clients in DB: Gitea, bincio-activity (client_id + secret)
- Decide on
bincio.orgas the OIDC issuer URL
Phase 1 — OIDC endpoints in bincio-auth [ ]
Additive — nothing existing changes. bincio-activity and wiki keep working unchanged.
- Add
oauth2_clientsandoauth2_codestables to DB schema GET /.well-known/openid-configuration— static JSON describing all endpoints + issuerGET /.well-known/jwks.json— RSA public key in JWK formatGET /oauth2/authorize— shows login form (or reuses existing session), issues auth codePOST /oauth2/token— validates code, returnsid_token(RS256 JWT) +access_tokenGET /oauth2/userinfo— returnssub,name,preferred_username, custom claims- PKCE support (
code_challenge/code_verifier) for public clients - Smoke test: complete a full OIDC flow with
curl/ a test script
Phase 2 — Gitea [ ]
- Install Gitea on VPS (systemd service, port 3000)
- nginx server block for
git.bincio.orgwith Let's Encrypt cert - Configure Gitea: disable self-registration, set admin account
- Add "Sign in with bincio" — Generic OAuth2 source pointing to bincio-auth OIDC
- Test: existing bincio users sign in → Gitea auto-provisions their profile
- Mirror bincio repos (bincio-activity, bincio-auth, bincio-autarchive, bincio-rec, bincio-wiki)
Phase 3 — Migrate bincio-activity to RS256 [ ]
The only phase with live-service risk — handled with a feature flag.
- bincio-activity fetches JWKS from bincio-auth and caches the public key
- Add RS256 validation path alongside existing HS256 path (feature flag:
--oidc-issuer) - Deploy with both paths active, verify RS256 works for all users
- Remove HS256 path +
BINCIO_AUTH_JWT_SECRETenv var - Rollback plan: keep
--jwt-secretflag functional until RS256 is confirmed stable
Phase 4 — Email / SMTP [ ]
- SMTP config in bincio-auth (
--smtp-host,--smtp-user,--smtp-password,--smtp-from, env vars) - Self-service password reset: user requests via email → gets link → resets without admin
- Keep admin-code endpoint as fallback for users without a registered email
- Optional: email verification on registration
Phase 5 — Mobile app (PKCE) [ ]
- Register mobile app as a public OIDC client (no client_secret, PKCE only)
- App opens browser to
/oauth2/authorize, handles redirect back via deep link - Exchange code for id_token via
/oauth2/token(PKCE verifier instead of client_secret) - Remove current
/api/auth/tokenmobile workaround
What stays the same throughout
bincio.org/login/— same login page, same UX until Phase 4- All existing sessions remain valid during Phases 1–2
- bincio-activity users notice nothing during Phases 1–2
bincio_sessioncookie name and domain unchanged
Risk register
| Risk | Mitigation |
|---|---|
| Phase 3 breaks bincio-activity login | Feature flag: run HS256 + RS256 in parallel for one deploy cycle |
| RSA key lost / corrupted | Back up /etc/bincio/oidc_private.pem offsite; all active sessions would be invalidated but users just log in again |
| SMTP deliverability from VPS IP | Use a dedicated sending service (Postmark/Brevo), not raw VPS SMTP |
| Gitea DB diverges from bincio-auth | Gitea is read-only consumer — bincio-auth is authoritative; Gitea account = auto-provisioned on first OIDC login |
Effort estimate
| Phase | Estimate |
|---|---|
| 0 — prep | ~1 hour |
| 1 — OIDC endpoints | ~1.5–2 days |
| 2 — Gitea | ~2–3 hours |
| 3 — bincio-activity RS256 | ~2–3 hours |
| 4 — email/SMTP | ~3–4 hours |
| 5 — mobile PKCE | ~1 day |