hoangsonww / hoangsonww/Passwordless-Auth-Rust
Feature: Minimal OpenID Connect (OIDC) Provider — Auth Code + PKCE
- Dominant language
- Rust
- Stars
- 15
- Forks
- 7
- PR merge metrics
- No merged PRs in 30d
Description
**Summary**
Expose Passwordless-Auth as a first-party **OIDC Identity Provider** so apps can use standard OIDC/OAuth2 instead of bespoke JWT exchange. MVP supports **Authorization Code + PKCE**, **RS256/ES256-signed ID tokens**, discovery (`.well-known`), JWKS, and `userinfo`.
**Why**
* Works with off-the-shelf OIDC clients (NextAuth, Keycloak adapters, Spring, Auth.js, etc.).
* Keeps the server passwordless (magic link / WebAuthn / TOTP) while presenting a standard OAuth layer to relying parties.
* Enables multi-app SSO, token introspection, and gradual ecosystem integration.
---
## Scope (MVP)
### Endpoints
* `GET /.well-known/openid-configuration`
* `GET /.well-known/jwks.json` (serve active + previous JWKs; include `kid`)
* `GET /oauth/authorize` (consent + auth code issue; **PKCE required**)
* `POST /oauth/token` (code → tokens; refresh support; returns `id_token`, `access_token`, `refresh_token`)
* `GET /oauth/userinfo` (email, email\_verified, sub, name?)
### Supported grant/response
* **Authorization Code** with **PKCE (S256 only)**
* Response types: `code`
* Scopes: `openid` (required), `email`, `profile`, `offline_access` (maps to refresh token)
### Tokens & claims
* **ID Token**: `iss`, `sub` (stable UUID), `aud`, `iat`, `exp`, `auth_time`, `nonce`, `email`, `email_verified`
* **Access Token**: opaque or JWT (JWT is fine for now; sign with same key)
* **Refresh Token**: existing mechanism reused; tie to client\_id
### Crypto / keys
* Switch to **asymmetric signing** for OIDC: **ES256** (preferred) or **RS256**.
* Persist a **JWK keystore** (SQLite table) with `kid`, `alg`, `created_at`, `expires_at`, `active`.
* **Key rotation** CLI: `passwordless-auth keys rotate` → publishes new JWK while keeping last one for validation.
* `.well-known/jwks.json` exposes active + last non-expired keys.
### Clients & consent
* Static client registry (config + DB): `client_id`, hashed `client_secret` (optional for public clients), `redirect_uris[]`, `allowed_scopes[]`, `first_party`.
* Consent screen (HTML minimal) or **auto-consent** for `first_party=true`.
* Enforce exact `redirect_uri` match (no wildcards).
### Flows (UX)
1. RP sends user to `/oauth/authorize?client_id=...&redirect_uri=...&scope=openid%20email&response_type=code&state=...&code_challenge=...&code_challenge_method=S256&nonce=...`
2. If not authenticated, server drives **passwordless login** (magic link/WebAuthn/TOTP).
3. Issue short-lived **auth code** (e.g., 60–120s), bind to `code_challenge`, `client_id`, `redirect_uri`, `nonce`.
4. Token exchange at `/oauth/token` with `code_verifier` returns tokens.
5. RP may call `/oauth/userinfo`.
### Config (TOML)
```toml
[oidc]
enable = true
issuer = "https://auth.example.com"
require_pkce = true
id_token_ttl_seconds = 900
access_token_ttl_seconds = 900
refresh_token_ttl_seconds = 604800
[oidc.keys]
alg = "ES256" # or "RS256"
jwks_store = "jwks.db" # or reuse sqlite
[[oidc.clients]]
client_id = "myapp-web"
first_party = true
redirect_uris = ["https://myapp.example.com/callback"]
allowed_scopes = ["openid","email","profile","offline_access"]
public_client = true # PKCE required, no client_secret
```
---
## Acceptance Criteria
* `/.well-known/openid-configuration` & `/.well-known/jwks.json` pass basic OIDC discovery checks.
* End-to-end **Auth Code + PKCE** works with a sample OIDC client (e.g., Auth.js / NextAuth) using `openid email`.
* `id_token` includes correct `aud`,`nonce`,`email_verified`; signature verifies against JWKS.
* Exact `redirect_uri` matching; `state` echoed; `nonce` validated.
* Auth code single-use, short TTL; replay rejected.
* PKCE enforced (S256 only); plain rejected.
* Key rotation does not break existing tokens; JWKS serves both old/new until old expiry.
* Rate limiting on `/oauth/authorize` + `/oauth/token` to mitigate abuse.
* Unit + integration tests (happy path, bad `code_verifier`, wrong `redirect_uri`, expired code, JWKS kid swap).
---
## Security Notes
* **Asymmetric signing** is mandatory for OIDC (don’t expose `jwt_secret`).
* Enforce HTTPS in production; verify `origin`/`rp_id` alignment for WebAuthn step.
* Short code TTL; bind code to `client_id` + `redirect_uri` + `code_challenge`.
* Strict CORS on `/oauth/*` as needed; CSRF not applicable to GET auth but keep best practices on consent POST if any.
* Store client\_secrets salted/hashed (if using confidential clients).
---
## Out of Scope (v1)
* Dynamic client registration, PAR, Device Code, Back-channel logout, JWT Secured Authorization Request, mTLS, Federation.
* Admin UI (CLI + config only for now).
---
## Migration / Docs
* New `oidc` section in `config.toml`.
* Add `docs/oidc.md` with copy-paste client configs (NextAuth, Spring Security, OAuth2 Proxy).
* Example: `example/oidc-nextjs/` minimal app for manual verification.
Contributor guide
Assessment
This issue has not been assessed yet.