| .. | ||
| app.py | ||
| config.py | ||
| oidc_client.py | ||
| pyproject.toml | ||
| README.md | ||
| templates.py | ||
| uv.lock | ||
Porchlight OIDC RP — reference implementation
A small, standalone Relying Party that authenticates users against a running porchlight OpenID Provider.
It hand-rolls every OIDC step (no OIDC client library) so the protocol is
readable. PyJWT is used only for the RS256 signature primitive and JWK
parsing — the OIDC claim checks (iss / aud / exp / nonce) are written
out explicitly in oidc_client.py.
Not part of porchlight's shipped code. Lives here as a teaching reference and is intentionally left untracked by git.
What it demonstrates
- Discovery — fetching
/.well-known/openid-configuration. - Authorization Code flow with PKCE (S256), plus
stateandnonce. - Token exchange using
client_secret_basicauth. - ID token verification — signature via JWKS, then explicit claim checks.
- UserInfo — calling the endpoint with the access token.
- Refresh — using the refresh token (requires
offline_access). - Logout — local only. Porchlight exposes no
end_session_endpoint, so global single-logout at the OP is not possible; see the comment inapp.py.
1. Register this RP in porchlight
Generate a client secret:
python -c "import secrets; print(secrets.token_urlsafe(48))"
Add a client to your porchlight.toml (paste the secret above):
[clients.showcase-rp]
client_secret = "<paste-generated-secret>"
redirect_uris = ["http://localhost:9000/callback"]
response_types = ["code"]
scope = ["openid", "profile", "email", "offline_access"]
token_endpoint_auth_method = "client_secret_basic"
Restart porchlight so it picks up the new client.
2. Run porchlight (OP) on :8000
From the repo root:
OIDC_OP_ISSUER=http://localhost:8000 OIDC_OP_DEBUG=true \
OIDC_OP_CONFIG_FILE=porchlight.toml \
uv run uvicorn porchlight.app:create_app \
--factory --host 127.0.0.1 --port 8000 --reload --reload-dir src
Make sure you have at least one user to log in as (see the porchlight CLI:
uv run porchlight initial-admin / create-invite).
3. Run this RP on :9000
cd examples/rp-reference
# tell the RP the client secret you generated in step 1
export OIDC_RP_CLIENT_SECRET="<paste-generated-secret>"
uv run --with-requirements <(uv pip compile pyproject.toml 2>/dev/null) \
uvicorn app:create_app --factory --port 9000 --reload
Or, more simply, let uv resolve the local project:
cd examples/rp-reference
uv run uvicorn app:create_app --factory --port 9000 --reload
4. Walk through the flow
- Open http://localhost:9000/.
- Click Login with Porchlight → you're redirected to porchlight.
- Authenticate and approve the consent screen.
- You land back on the result page showing:
- the verified ID token claims,
- the UserInfo response,
- the raw token response.
- Click Refresh tokens — note the access token changes and the refresh
token rotates, but no new
id_tokenis issued (porchlight does not re-mint ID tokens on refresh). - Click Log out — clears this RP's session only.
Configuration
All optional; defaults assume localhost. Override with env vars:
| Variable | Default | Meaning |
|---|---|---|
OIDC_RP_ISSUER |
http://localhost:8000 |
porchlight issuer URL |
OIDC_RP_CLIENT_ID |
showcase-rp |
must match the porchlight.toml client |
OIDC_RP_CLIENT_SECRET |
change-me |
the generated secret |
OIDC_RP_REDIRECT_URI |
http://localhost:9000/callback |
must match a registered redirect_uri |
OIDC_RP_SCOPE |
openid profile email offline_access |
requested scopes |
OIDC_RP_SESSION_SECRET |
dev default | signs the RP session cookie (not the OIDC secret) |
OIDC_RP_LEEWAY |
30 |
allowed clock skew (s) for exp/iat |
Files
| File | Purpose |
|---|---|
oidc_client.py |
the hand-rolled OIDC steps — start reading here |
app.py |
FastAPI routes + in-memory session handling |
templates.py |
minimal inline HTML |
config.py |
env-var configuration |