reference RP
This commit is contained in:
parent
850240ab97
commit
8e8c33a407
7 changed files with 1508 additions and 0 deletions
114
examples/rp-reference/README.md
Normal file
114
examples/rp-reference/README.md
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
# Porchlight OIDC RP — reference implementation
|
||||
|
||||
A small, standalone [Relying Party](https://openid.net/specs/openid-connect-core-1_0.html)
|
||||
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`](./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:
|
||||
|
||||
```bash
|
||||
python -c "import secrets; print(secrets.token_urlsafe(48))"
|
||||
```
|
||||
|
||||
Add a client to your `porchlight.toml` (paste the secret above):
|
||||
|
||||
```toml
|
||||
[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:
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
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 |
|
||||
Loading…
Add table
Add a link
Reference in a new issue