porchlight/examples/rp-reference/README.md
Johan Lundberg 8e8c33a407
reference RP
2026-06-29 09:23:22 +02:00

4 KiB

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

  1. Discovery — fetching /.well-known/openid-configuration.
  2. Authorization Code flow with PKCE (S256), plus state and nonce.
  3. Token exchange using client_secret_basic auth.
  4. ID token verification — signature via JWKS, then explicit claim checks.
  5. UserInfo — calling the endpoint with the access token.
  6. Refresh — using the refresh token (requires offline_access).
  7. Logout — local only. Porchlight exposes no end_session_endpoint, so global single-logout at the OP is not possible; see the comment in app.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

  1. Open http://localhost:9000/.
  2. Click Login with Porchlight → you're redirected to porchlight.
  3. Authenticate and approve the consent screen.
  4. You land back on the result page showing:
    • the verified ID token claims,
    • the UserInfo response,
    • the raw token response.
  5. Click Refresh tokens — note the access token changes and the refresh token rotates, but no new id_token is issued (porchlight does not re-mint ID tokens on refresh).
  6. 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