This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
mini-idp is an educational standalone identity provider in Java. It registers clients, issues short-lived Ed25519-signed JWT access tokens via the OAuth 2.0 client-credentials grant, and publishes its public signing keys (JWKS) so a verifier can validate tokens offline. Like its sibling project mini-kms, it is heavily commented on purpose — the code is meant to be read to learn how token issuance and offline JWS verification work. It uses real, sound crypto (Argon2id for secrets, Ed25519/EdDSA for signatures) but is explicitly not production-audited. Clarity and correct security reasoning matter more than features.
mini-idp is step 1 of evolving mini-kms away from a shared static token toward per-client
identities: its tokens carry an authorization claim (grants) that maps cleanly onto mini-kms's
Principal / KeyOperation model. This project does not depend on mini-kms — it only mirrors
its conventions and targets its eventual verification contract.
Requires JDK 21+ (the Gradle toolchain is pinned to 21; it can auto-download via the foojay resolver).
./gradlew build # compile + run ALL tests (this is the CI check)
./gradlew test # tests only, all modules
./gradlew :core:test # one module
./gradlew :core:test --tests "*TokenLifecycleTest" # one class
./gradlew :core:test --tests "*TokenLifecycleTest.expiredTokenIsRejected" # one method
./gradlew :server:installDist # produce a runnable launcher: server/build/install/server/bin/serverThere is no separate linter/formatter; ./gradlew build is the full gate. Tests are JUnit 5.
Gradle's configuration cache is on (org.gradle.configuration-cache=true), so build-script
changes may need --no-configuration-cache while iterating.
The bootstrap admin token comes from an env var or a file, never a CLI arg, and is never logged.
export MINIIDP_ADMIN_TOKEN="$(openssl rand -hex 32)"
server/build/install/server/bin/server --port 8455 --data-dir ~/.mini-idp
# Register a client (admin), then exchange its credentials for a token:
curl -s -X POST localhost:8455/admin/clients -H "Authorization: Bearer $MINIIDP_ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"displayName":"svc","authorization":{"control":false,"groups":[{"keyGroup":"billing","operations":["ENCRYPT","DECRYPT"]}]}}'
curl -s -X POST localhost:8455/oauth/token \
-d 'grant_type=client_credentials&client_id=CLIENT&client_secret=SECRET'
# Browse the API: http://localhost:8455/docs (Swagger UI, vendored assets, works offline)Two Gradle modules under base package com.codeheadsystems.miniidp:
core— the identity model, crypto, and token machinery, with no HTTP/transport code. This separation is load-bearing (mirrors mini-kms's I/O-freecore): the services and crypto can be reused and tested without a server. Owns the authorization model, Argon2id secret hashing, Ed25519 keys, the hand-rolled JWS/JWT, the JWKS model, the JSON file stores, and the services built on them.server— the HTTP daemon (ServerMain): a JDKcom.sun.net.httpserver.HttpServeron loopback, the tiny router, the request handlers, config, and the OpenAPI spec + vendored Swagger UI. Depends oncore.
The published contract is server/src/main/resources/openapi.yaml (served at /openapi.yaml,
/openapi.json, rendered at /docs). The JWT claim schema there is authoritative. The key piece
is the grants claim, produced by token/GrantsClaim from a domain auth/Authorization:
sub(clientId) → mini-kmsPrincipal.idgrants.control→ mini-kmsPrincipal.admingrants.groups[](keyGroup+KeyOperationnames) → mini-kmsKeyAuthorizationPolicy
auth/KeyOperation is a deliberate mirror of mini-kms's enum — the string values are the
contract, so do not rename them. A cnf claim is reserved (RFC 7800) for future channel binding
but is not populated or enforced yet.
- Ed25519 signing uses only the JDK (
KeyPairGenerator/Signature"Ed25519").crypto/Ed25519Keyshandles encoding; the JWKxvalue is the trailing 32 bytes of the SPKI. - The JWS is hand-rolled in
token/Jws(commented heavily, the way mini-kms hand-rolls its envelope formats) rather than via a JOSE library:base64url(header).base64url(payload). base64url(sig), signed over the ASCII of the first two segments.token/TokenVerifieris the reference offline verifier (signature first, theniss/aud/time/revocation). - Client secrets are hashed with Argon2id (
secret/Argon2SecretHasher, mirrors mini-kms'sArgon2KeyDeriver) and verified in constant time (MessageDigest.isEqual).
- All state (clients, signing keys, revocations, audit) is JSON written via
store/JsonStorewith the atomic temp-file +ATOMIC_MOVE+ 0600 pattern (mirrors mini-kms'sKeystore). - Private signing keys live in
signing-keys.json(0600). A real deployment would wrap them under a KMS — that is the eventual recursive integration with mini-kms, intentionally out of scope. The marker is inmodel/SigningKeyRecord. - Rotation (
service/SigningKeyService) activates a fresh key and retires the old one; retired keys stay in the JWKS (retiredKeyRetention, = 2× token TTL) so tokens signed just before a rotation still verify until they expire.
corestays HTTP-free. Nocom.sun.net.httpserver, sockets, or request parsing belong there. The composition root isserver/IdpServer.-parameterscompilation is required — Jackson deserializes the store records and token claim records by constructor parameter names. Don't remove the compiler flag (set in the root build) or Jackson record binding breaks.- No oracles, no leaks. The token endpoint returns a single generic
invalid_clientfor any authentication failure (no unknown-client vs. wrong-secret distinction). Client secrets, private keys, and raw tokens are never logged; access logs record method/path/status only. - Secrets via env/file, never argv. The admin token follows the
resolveAdminTokenpattern. - Use
System.Logger(already the pattern), not a third-party logging dependency. - The OpenAPI spec is the contract.
OpenApiContractTestfails if a documented path/method doesn't resolve on the live server, so keepopenapi.yamland the routes inApiHandlersin sync.
README.md— how to run it, the full endpoint list, the token claim schema, and the JWKS/discovery URLs (the exact contract mini-kms will consume next).