Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ mini-auth/
│ ├── mini-idp/ {core, server} (shipping)
│ ├── mini-oidc/ (shipping; human SSO / OpenID Provider, embeds pk-auth)
│ ├── mini-gateway/ (shipping; forward-auth for a reverse proxy)
│ ├── mini-directory/ (shipping; identity source of truth, standalone)
│ ├── mini-directory/ (shipping; identity source of truth; mini-idp reads service accounts from it)
│ ├── mini-ca/ (shipping; internal CA, CA key wrapped under mini-kms)
│ └── mini-console/ (roadmap placeholder)
└── libs/ # shared libraries (no transport)
Expand Down Expand Up @@ -153,7 +153,7 @@ mini-auth/
must still pass unchanged.
- **The token-claim contract aligns across the family.** mini-idp's `grants` claim maps onto
mini-kms's authorization model (`sub → Principal.id`, `grants.control → Principal.admin`,
`grants.groups[] → KeyAuthorizationPolicy`); `auth/KeyOperation` string values are the contract —
`grants.groups[]`a per-key-group `PolicyEngine` decision (mini-policy)); `auth/KeyOperation` string values are the contract —
don't rename them. Preserve this mapping in mini-token / mini-policy work. **Note this is a
*designed* contract, not a wired runtime path:** `KmsRequestHandler` still authenticates with a
shared per-plane token + fixed principals and ships `AllowAllPolicyEngine` — it does not yet parse a JWT
Expand Down Expand Up @@ -208,7 +208,7 @@ services/mini-kms/client/build/install/client/bin/kms-admin --tcp 127.0.0.1:9123
- **Two planes, two tokens.** Every `RequestType` is tagged `DATA` or `CONTROL` (`RequestPlane`).
`KmsRequestHandler` (in `core`) routes by plane and validates the matching token — the **API
token** for data ops, a **separate admin token** for control ops. Data-plane ops also pass
through `KeyAuthorizationPolicy` per key group; the shipped `AllowAllPolicyEngine` is the documented
through a per-key-group `PolicyEngine` decision (mini-policy); the shipped `AllowAllPolicyEngine` is the documented
seam for per-client authz later (the thing `mini-policy` generalizes).
- **The two seams.** `KmsRequestHandler` depends only on `MasterKeyProvider` (data:
`wrap/unwrap/encrypt/decrypt/keyIdOf`) and `KeyringManager` (control:
Expand Down Expand Up @@ -280,7 +280,7 @@ services/mini-idp/server/build/install/server/bin/server --port 8455 --data-dir
- **The token contract.** `server/src/main/resources/openapi.yaml` (served at `/openapi.yaml`,
`/openapi.json`, `/docs`) is authoritative. The `grants` claim (mini-token's
`token/GrantsClaim` over `auth/Authorization`) maps to mini-kms: `sub → Principal.id`,
`grants.control → Principal.admin`, `grants.groups[] → KeyAuthorizationPolicy`. `auth/KeyOperation`
`grants.control → Principal.admin`, `grants.groups[]`a per-key-group `PolicyEngine` decision (mini-policy). `auth/KeyOperation`
is a **deliberate mirror** of mini-kms's enum — the string values are the contract, do not rename
them. A `cnf` claim is reserved (RFC 7800) but not enforced yet.
- **Crypto & formats (hand-rolled bits, now in mini-token).** Ed25519 via the JDK only
Expand Down Expand Up @@ -310,8 +310,10 @@ mini-directory is the **single identity source of truth** for the family: it own
job is **resolution** — turning any stored account into a **mini-policy `Principal` plus a
fully-expanded, de-duplicated set of `(action, resource)` grants** (roles expand to grants; group
memberships are inherited), which is exactly what a `mini-policy` decision function consumes. It is
**shipping but standalone**: the issuers do **not** read from it yet (that is Phase 3/4 work; see the
open client-registry question in `docs/DIRECTION.md`).
**shipping and wired in**: mini-idp reads its service accounts from here at every `/oauth/token`
(required — `--directory-url`; mini-idp keeps no local client registry), and mini-oidc resolves
humans from here when `--directory-url` is set (optional, in-memory fallback otherwise). See the
resolved client-registry decision in `docs/DIRECTION.md`.

**Run it locally.** The bootstrap admin token comes from an env var or a file, **never a CLI arg,
and is never logged**. Loopback by default.
Expand Down
15 changes: 7 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,14 @@ mini-auth is two things:
of the family: the vision, the catalog, the architecture, the recursive integrations, the open
design questions, and the roadmap — the full architecture reference.

> 🎓 **Want to learn how auth works (not just read the code)? Start with
> [docs/TEACHING.md](docs/TEACHING.md)** — a course-style track (concepts → hands-on labs →
> security) that teaches authentication & authorization using the family as the worked example.
> 🎓 **New here? Start with [docs/TEACHING.md](docs/TEACHING.md).** It's a course-style track
> (concepts → hands-on labs → security) that teaches authentication & authorization using the family
> as the worked example, and it routes you to the right next step in one screen.
>
> 🧭 **Prefer to read the source in order? [docs/LEARNING.md](docs/LEARNING.md)** is a guided reading
> order that walks the family in dependency order, naming the concept each stop teaches. Keep
> **[docs/GLOSSARY.md](docs/GLOSSARY.md)** open for the crypto / OAuth / OIDC vocabulary, and read
> [docs/DIRECTION.md](docs/DIRECTION.md) once you want the whole architecture map.
> *(Concepts-first → `TEACHING.md`; code-first → `LEARNING.md`.)*
> It links you onward to the other entry points when you need them: **[docs/LEARNING.md](docs/LEARNING.md)**
> if you'd rather read the *source* in dependency order (code-first instead of concepts-first),
> **[docs/GLOSSARY.md](docs/GLOSSARY.md)** for the crypto / OAuth / OIDC vocabulary, and
> **[docs/DIRECTION.md](docs/DIRECTION.md)** for the whole architecture map.

> ⚠️ **Educational project.** The family uses real, sound cryptographic constructions but is **not
> audited** and is not a substitute for production identity infrastructure. Six services and both
Expand Down
22 changes: 12 additions & 10 deletions docs/DIRECTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ Every mini, its one-line purpose, whether it is a library or a service, and its
| **mini-kms** | Envelope encryption / KMS: rotatable keys, the eventual vault that wraps other services' signing keys. | service (+ core/client libs) | **shipping** |
| **mini-idp** | Machine-to-machine identity: OAuth2 client-credentials → Ed25519 JWT, JWKS. | service (+ core lib) | **shipping** |
| **mini-token** | The shared token plane: JWS, JWKS, signing-key lifecycle, rotation, revocation, audit, the `grants` claim contract, a persistence SPI, the offline `JwsClaimsVerifier`, AND the shared browser-SSO `SessionService` (so mini-oidc and mini-gateway share one session store). | library | **shipping** |
| **mini-policy** | Generalized authorization decision function: `(principal, action, resource) → allow/deny`. Generalizes mini-kms's `KeyAuthorizationPolicy`. | library | **shipping** (minimal engine; consumed by directory/oidc/gateway/kms — family-wide grant sourcing is future) |
| **mini-policy** | Generalized authorization decision function: `(principal, action, resource) → allow/deny`. Generalizes mini-kms's per-key-group key-authorization policy into one engine. | library | **shipping** (minimal engine; consumed by directory/oidc/gateway/kms — family-wide grant sourcing is future) |
| **mini-oidc** | Human SSO / OpenID Provider: authorization-code + PKCE, ID + access + refresh tokens, /userinfo, browser SSO sessions, single logout, login/consent UI. Embeds **pk-auth** (passkeys + backup-code recovery), mints via **mini-token**, authorizes scopes via **mini-policy**, resolves users from **mini-directory**. | service | **shipping** |
| **mini-gateway** | Forward-auth endpoint for a reverse proxy (Traefik / Caddy / nginx `auth_request`) to gate apps with no native auth: validates the shared mini-oidc SSO session or a bearer token, decides per-route via mini-policy, returns allow / 401 / 403 / redirect-to-login. | service | **shipping** |
| **mini-directory** | The single identity source of truth: humans, groups, roles, service accounts, and their grant mappings; resolves any account to a mini-policy `Principal` + expanded grants. | service | **shipping** (standalone; issuers not yet wired to read from it) |
| **mini-directory** | The single identity source of truth: humans, groups, roles, service accounts, and their grant mappings; resolves any account to a mini-policy `Principal` + expanded grants. | service | **shipping** (mini-idp reads service accounts from it — required, no local registry; mini-oidc resolves humans from it when `--directory-url` is set) |
| **pk-auth** | Passkeys-first auth library set, published on Maven Central under `com.codeheadsystems`. Consumed as a normal dependency — **not vendored**. | external library | **shipping (external)** |
| **mini-ca** | Small internal certificate authority for mTLS between the minis and workload identity in the homelab. Issues/renews short-lived leaves from CSRs; its CA key is wrapped under mini-kms. | service (application) | **shipping** |
| **mini-console** | Optional unified admin UI over the family. | service (future) | **roadmap** (placeholder module) |
Expand Down Expand Up @@ -150,8 +150,8 @@ the load-bearing relationships:
mini-idp wrote first, so the two issuers stop diverging.
- **All services evaluate through mini-policy.** mini-gateway gating a route, mini-kms gating a
key group, the issuers checking a scope — each becomes a `PolicyRequest(principal, resource,
action)` against one engine. mini-policy is the generalization of mini-kms's
`KeyAuthorizationPolicy`.
action)` against one engine. mini-policy generalizes mini-kms's former per-key-group
key-authorization policy into the shared `PolicyEngine`.
- **mini-token's signing keys are wrapped by mini-kms (the recursive integration — *done*).** The
default educational path still stores the Ed25519 private key locally (`0600`), but with the
`--kms-*` config the auth services wrap each signing key under a mini-kms key group: only the
Expand All @@ -166,7 +166,7 @@ the load-bearing relationships:

The claim payload already lines up across the family. A mini-idp token's `grants` claim maps
directly onto mini-kms's authorization model (`sub → Principal.id`, `grants.control →
Principal.admin`, `grants.groups[] → KeyAuthorizationPolicy`). mini-token preserves that mapping;
Principal.admin`, `grants.groups[]`a per-key-group `PolicyEngine` decision (mini-policy)). mini-token preserves that mapping;
mini-policy is where it is evaluated; mini-directory is where the grants originate.

> **Wired vs. designed — read this before tracing the token → mini-kms path.** The mapping above is
Expand Down Expand Up @@ -343,18 +343,20 @@ extraction only promoted `Base64Url`/`RandomIds`, which it directly needed; the
clear second consumer to pin them against.

**Phase 2 — Generalize authorization.** Flesh out **mini-policy** into a real engine and adopt it
behind mini-kms's `KeyAuthorizationPolicy` seam first (it already has the right shape), then behind
behind mini-kms's per-key-group `PolicyEngine` seam first (it already has the right shape), then behind
the issuers' scope checks.

**Phase 3 — Stand up the directory.** *Standalone service: done.* **mini-directory** now has a real
identity model (humans, groups, roles, service accounts), an admin CRUD API behind the family's
bootstrap admin token, atomic-`0600` persistence, an OpenAPI spec + vendored Swagger UI, and the
defining capability: resolving any account into a mini-policy `Principal` plus its fully-expanded,
de-duplicated grants (roles expand to grants; group memberships are inherited). Service-account
secrets are Argon2id-hashed at rest. What remains is **wiring the issuers to read from it** — mini-idp
resolving service accounts, mini-oidc resolving humans — and the open question below about folding
mini-idp's client registry in. That integration is deliberately not done yet; the service stands
alone today.
secrets are Argon2id-hashed at rest. **The issuers now read from it.** mini-idp resolves service
accounts here at every `/oauth/token` (via `HttpServiceAccountDirectory` → the directory's
`/admin/service-accounts/authenticate`) and `--directory-url` is **required** — mini-idp no longer
keeps a local client registry, which resolved the open question about folding it in. mini-oidc
resolves humans here too, via its `UserDirectory` SPI, when `--directory-url` is set. What remains is
the token → mini-kms *authorization* seam (below) — not the directory wiring, which is done.

**Phase 4 — Human SSO.** *Done.* **mini-oidc** ships: the authorization-code + PKCE flow, ID +
access tokens minted via mini-token (offline-verifiable against the shared JWKS) + rotating refresh
Expand Down
30 changes: 26 additions & 4 deletions docs/GLOSSARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ The vocabulary deliberately follows real systems (AWS/GCP KMS, NIST, the relevan
RFCs) so the concepts carry over. Each entry says what the term is, **why it matters
here**, and where it lives in the code.

> New to the project? Read this alongside [`DIRECTION.md`](DIRECTION.md), then follow
> the reading order in [`LEARNING.md`](LEARNING.md).
> New to the project? Start with the course front door [`TEACHING.md`](TEACHING.md), and keep this
> open alongside it. For the architecture map see [`DIRECTION.md`](DIRECTION.md); to read the source
> in dependency order see [`LEARNING.md`](LEARNING.md).

---

Expand Down Expand Up @@ -107,6 +108,15 @@ The token plane lives in **mini-token** (`libs/mini-token`); the issuers are **m
- **Revocation / denylist** — a list of token ids (`jti`) a verifier can consult to reject
a specific token before its natural expiry (`mini-token` `RevocationService`). Short TTLs
are the primary control; revocation is the early kill switch.
- **CSRF (cross-site request forgery)** — an attack where a malicious site causes a logged-in
user's browser to make an unwanted state-changing request using the user's ambient cookies. The
family defends it two ways: a **`SameSite=Lax`** session cookie (the browser won't send it on
cross-site POSTs), and a **per-pending-authorization CSRF token** required on every state-changing
browser POST in mini-oidc (`OidcHandlers`, constant-time checked).
- **`cnf` (confirmation / proof-of-possession claim, RFC 7800)** — a token claim that would bind
the token to a key the holder must prove possession of, so a *stolen* token is useless without
that key (turning a bearer token into a sender-constrained one). **Reserved but not enforced** in
the family today (`TokenIssuer` writes it null) — an honest seam, not a live control.

---

Expand Down Expand Up @@ -136,8 +146,14 @@ The decision model is **mini-policy** (`libs/mini-policy`); the identity source
by mini-idp); humans carry no secret and authenticate with passkeys (via mini-oidc).
- **`grants` claim** — mini-token's per-key-group authorization claim (`token/GrantsClaim`
over `auth/Authorization`) that maps onto mini-kms's model: `sub → Principal.id`,
`grants.control → Principal.admin`, `grants.groups[] → KeyAuthorizationPolicy`. The
string values of `auth/KeyOperation` are a deliberate contract — do not rename them.
`grants.control → Principal.admin`, `grants.groups[] →` a per-key-group `PolicyEngine`
decision (mini-policy). The string values of `auth/KeyOperation` are a deliberate contract —
do not rename them.
- **Least privilege** — grant each principal the *minimum* authority it needs, and no more
(narrow grants, scoped tokens, short TTLs, no blanket wildcards). It's the design discipline
behind **deny-by-default** (the mechanism that enforces it) and behind the two-plane / two-token
splits across the family. The opposite — broad standing authority — is what turns one compromise
into total compromise.

---

Expand Down Expand Up @@ -180,6 +196,12 @@ From **mini-ca** (`services/mini-ca`), an internal CA for mTLS.
endpoint before forwarding each request, to gate upstreams that have no auth of their own.
**mini-gateway**'s `/verify` is that endpoint; it reuses the shared session + JWS
verification and answers allow/deny.
- **Confused deputy** — a trusted component tricked into misusing its authority on behalf of a
less-privileged caller. Forward-auth is a classic setting: a client could try to *forge* the
`X-Auth-*` identity headers, or reach `/verify` directly with a spoofed `X-Forwarded-Uri`, to make
the proxy/gateway act on attacker-chosen input. mini-gateway defends it by **always overwriting**
the `X-Auth-*` headers (so injected ones are discarded) and by being reachable **only by the
trusted proxy**, never by clients directly.
- **Data plane vs. control plane** — the split between per-request operations (guarded by an
API token) and management operations (guarded by a separate admin token); mini-kms tags
every request type with its plane.
Expand Down
15 changes: 8 additions & 7 deletions docs/TEACHING-OUTLINE.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Teaching mini-auth — documentation outline
# Teaching mini-auth — documentation outline (historical design doc)

> **What this file is.** A blueprint for a new, course-style documentation set under `docs/`
> that teaches **how authentication and authorization work in practice**, using the `mini-*`
> services as the worked example. It is the *plan* — each entry below becomes a real document.
> It does not replace the existing orientation docs; it sits on top of them.
> **What this file is.** The original *blueprint* for the course-style documentation set under
> `docs/` that teaches **how authentication and authorization work in practice**, using the `mini-*`
> services as the worked example. It is kept as a design/history record of how the set was planned.
>
> **Status:** outline / proposal. Nothing here is written yet except this file.
> **Status:** ✅ realized. The set this file planned now exists — see the concepts, tutorials, howtos,
> diagrams, and security track under `docs/`. **If you're here to learn, don't start here:** the live
> syllabus is [`TEACHING.md`](TEACHING.md). This outline is for contributors curious about the design.

---

Expand Down Expand Up @@ -114,7 +115,7 @@ them.
1. **token → mini-kms authorization is DESIGNED, not WIRED.** `KmsRequestHandler` uses a shared
per-plane bearer token + two fixed principals; it does not parse a JWT or read `grants`.
`GrantsClaim.toAuthorization()` has **no production caller.**
2. **mini-kms data plane ships `AllowAllPolicy`** — any authenticated caller is permitted.
2. **mini-kms data plane ships `AllowAllPolicyEngine`** — any authenticated caller is permitted.
3. **mini-oidc passkey enrolment (`/register/passkey/**`) is unauthenticated self-enrolment** —
a real deployment must gate it.
4. **mini-oidc `--directory-url` is optional**; without it, an empty in-memory directory resolves
Expand Down
4 changes: 1 addition & 3 deletions docs/TEACHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
> **What this is.** The front door to a course-style documentation set that teaches **how
> authentication and authorization actually work**, using the `mini-*` services as the worked
> example. It does not replace the orientation docs — it sits on top of them. One screen, then it
> routes you.
>
> The blueprint for the whole set is [`TEACHING-OUTLINE.md`](TEACHING-OUTLINE.md).
> routes you. **This is the live syllabus; start here.**

## How this set relates to the other docs

Expand Down
Loading