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)
- RP sends user to
/oauth/authorize?client_id=...&redirect_uri=...&scope=openid%20email&response_type=code&state=...&code_challenge=...&code_challenge_method=S256&nonce=...
- If not authenticated, server drives passwordless login (magic link/WebAuthn/TOTP).
- Issue short-lived auth code (e.g., 60–120s), bind to
code_challenge, client_id, redirect_uri, nonce.
- Token exchange at
/oauth/token with code_verifier returns tokens.
- RP may call
/oauth/userinfo.
Config (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.
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, anduserinfo.Why
Scope (MVP)
Endpoints
GET /.well-known/openid-configurationGET /.well-known/jwks.json(serve active + previous JWKs; includekid)GET /oauth/authorize(consent + auth code issue; PKCE required)POST /oauth/token(code → tokens; refresh support; returnsid_token,access_token,refresh_token)GET /oauth/userinfo(email, email_verified, sub, name?)Supported grant/response
codeopenid(required),email,profile,offline_access(maps to refresh token)Tokens & claims
iss,sub(stable UUID),aud,iat,exp,auth_time,nonce,email,email_verifiedCrypto / keys
kid,alg,created_at,expires_at,active.passwordless-auth keys rotate→ publishes new JWK while keeping last one for validation..well-known/jwks.jsonexposes active + last non-expired keys.Clients & consent
client_id, hashedclient_secret(optional for public clients),redirect_uris[],allowed_scopes[],first_party.first_party=true.redirect_urimatch (no wildcards).Flows (UX)
/oauth/authorize?client_id=...&redirect_uri=...&scope=openid%20email&response_type=code&state=...&code_challenge=...&code_challenge_method=S256&nonce=...code_challenge,client_id,redirect_uri,nonce./oauth/tokenwithcode_verifierreturns tokens./oauth/userinfo.Config (TOML)
Acceptance Criteria
/.well-known/openid-configuration&/.well-known/jwks.jsonpass basic OIDC discovery checks.openid email.id_tokenincludes correctaud,nonce,email_verified; signature verifies against JWKS.redirect_urimatching;stateechoed;noncevalidated./oauth/authorize+/oauth/tokento mitigate abuse.code_verifier, wrongredirect_uri, expired code, JWKS kid swap).Security Notes
jwt_secret).origin/rp_idalignment for WebAuthn step.client_id+redirect_uri+code_challenge./oauth/*as needed; CSRF not applicable to GET auth but keep best practices on consent POST if any.Out of Scope (v1)
Migration / Docs
oidcsection inconfig.toml.docs/oidc.mdwith copy-paste client configs (NextAuth, Spring Security, OAuth2 Proxy).example/oidc-nextjs/minimal app for manual verification.