This toy OIDC server is designed to provide a minimal OIDC server to develop and unit test against.
- Authorization Code flow with
openid,email,profile,groups, andoffline_accessscopes - PKCE (RFC 7636) with S256 and plain challenge methods
- Nonce validation for ID tokens
prompt(none,login,consent,select_account) andmax_age, with a very basic all-or-nothing consent screen that is never remembered.prompt=create, and any other unadvertised value, returns an HTTP 400 page- Refresh token support (via
offline_accessscope) - OIDC Discovery (
.well-known/openid-configuration) - JWKS endpoint for token verification
- End-session endpoint for single sign-out
Two suites, both run by uv run pytest:
tests/— in-process conformance tests against Flask's test client. Fast.tests/e2e/— the full browser-driven workflow. These start the provider and a real Relying Party (tests/e2e/rp_app.py) as subprocesses on ephemeral ports, then drive a browser through it with Playwright: the RP redirects to the provider, Playwright picks an authentication profile from the login page, the provider redirects back with a code, and the RP redeems it and uses the resulting tokens. They cover both client authentication methods (client_secret_basic,client_secret_post) against every PKCE mode (S256,plain, an implicit-plainchallenge with no method, and none), plus therefresh_tokengrant.
The end-to-end suite needs a browser once:
uv run playwright install chromiumRun one suite at a time with the e2e marker:
uv run pytest -m "not e2e" # in-process only
uv run pytest -m e2e # browser-driven onlyBoth suites build a throwaway SQLite database per run, because db.create_all()
does not alter existing tables — a stale app.db will be missing columns.
docker run --rm -p 8000:8000 -e SECRET_KEY=... ghcr.io/authenti-kate/tiny-oidc:latestPublished to GHCR
by .github/workflows/release.yml whenever a v* tag is pushed:
git tag -a v0.2.0 -m "..." && git push origin v0.2.0That builds linux/amd64 and linux/arm64, tags the image :0.2.0, :0.2,
:sha-<short> and :latest, and attaches build provenance. The test suite must
pass first.
The image runs as an unprivileged user and creates its SQLite database on first
boot. Do not mount a database made by an older version: db.create_all() adds
missing tables but never ALTERs an existing one.
It is very loosely based on details provided by:
- https://spapas.github.io/2023/11/29/openid-connect-tutorial/
- https://darutk.medium.com/diagrams-of-all-the-openid-connect-flows-6968e3990660
It was tested by running against https://openidconnect.net/ and https://github.com/BeryJu/oidc-test-client at various stages of development