MultiSite.md now describes the shipped feature for administrators — managing domains in the admin panel, sign-in sites, the auth host, related origins, cross-domain sign-in, OIDC discovery, CLI and 1.x upgrade notes — instead of the internal design narrative. oidc.md: realms -> domains, instance-global provider, current auth-code shapes.
4.5 KiB
OIDC Provider Implementation
OpenID Connect 1.0 provider enabling third-party apps to authenticate users via passkey. Also supports native cookie-based authentication.
Domains (multi rp-id)
The OIDC provider is instance-global: one signing key (oidc.key in the transaction log) and one client set for the whole instance, usable through every configured domain. Discovery, keys, token and userinfo endpoints resolve the issuer from the request host (domain dispatch), so every configured host is an issuer alias sharing the one key. Session.issuer records the issuing origin (scheme included, stamped from the WS Origin) so refresh and back-channel logout produce the right iss; Session.rp_id records the owning domain for display. CookieCode is stamped with the session's rp-id and verified at redemption; OIDCCode is not, since the provider is instance-global.
Data Models
User — Added: email, preferred_username
Session — Added: client_uuid (None = native, set = OIDC), issuer (origin that issued the session), rp_id (owning domain, display only)
key: bytes— hashed DB key, never stored rawsecret→hash_secret("session", secret)→ DB lookup- OIDC
sid→base64url.encode(hash_secret("oidc", session.key))
OIDClient — uuid, client_secret_hash, name, redirect_uris
Auth Codes (In-Memory Only)
60-second lifetime, auto-cleaned. Two separate stores keep the OIDC and cookie flows isolated:
from paskia.authcode import CookieCode, OIDCCode, store_cookie, store_oidc
class OIDCCode(msgspec.Struct):
session_key: str # Session DB key
created: datetime
redirect_uri, scope: str
nonce, code_challenge: str | None # PKCE S256 when provided
class CookieCode(msgspec.Struct):
session_key: str
created: datetime
rp_id: str # domain the code was issued in; checked at redemption
Usage: code = store_oidc(OIDCCode(...)) → later popped from oidc_codes / cookie_codes.
Authorization Flows
OIDC (Authorization Code)
GET /auth/restricted/oidc?client_id=UUID&redirect_uri=...&scope=openid&nonce=...&code_challenge=...- Frontend → WebSocket:
/auth/ws/authenticate?client_id=...&redirect_uri=...&... - Validate client/redirect_uri, authenticate via passkey
db.oidc_login()→(secret, session_key)- Create
AuthCode(session_key, oidc=OIDC(...))→ code - Return:
{"redirect_url": "{redirect_uri}?code={code}&state={state}"} - Client exchanges code at
/auth/oidc/tokenwithcode_verifier(PKCE S256)
Token: access_token, id_token, refresh_token={secret}, expires_in=3600
ID token: sub, sid (base64url), name, preferred_username, email, groups
Native (Cookie)
- WebSocket:
/auth/ws/authenticate(no OIDC params) - Authenticate via passkey
db.login()→ secret- Create
AuthCode(session_key=secret, oidc=None)→ exchange_code - Return:
{"user": "UUID", "exchange_code": "..."} POST /auth/api/exchangewith code → sets cookie
Refresh & Logout
Refresh: POST /auth/oidc/token with grant_type=refresh_token&refresh_token={secret}&client_id=...&client_secret=...
- Looks up session, validates client match
- Extends expiry +24h (sliding window)
- Returns new tokens with same
sid
Back-channel logout: POST /auth/oidc/backchannel-logout with logout_token={jwt}
- Verify signature, extract
sidorsub - Delete matched sessions
- Return 200 OK
Discovery: backchannel_logout_supported: true
Endpoints
GET /.well-known/openid-configuration— DiscoveryGET /auth/oidc/keys— Keys (EdDSA)POST /auth/oidc/token— Exchange/refreshGET /auth/oidc/userinfo— User (bearer token, includespicturewhenprofilescope is granted and avatar exists)POST /auth/oidc/backchannel-logout— LogoutPOST /auth/api/exchange— Native auth code → cookie
Claims
profilescope may includename,preferred_username, andpictureemailscope may includeemailgroupsis emitted from client-scoped permissions
Files
Created: paskia/authcode.py, paskia/util/crypto.py, paskia/fastapi/oid.py
Modified: paskia/db/structs.py, paskia/db/operations.py, paskia/fastapi/ws.py, paskia/fastapi/api.py, paskia/domains.py, paskia/fastapi/mainapp.py