Repo: darkedges/pingfederate-graph-broker*
A broker that holds refresh tokens is a high-value target. This part walks through the controls that protect them, and the trade-offs the starter deliberately accepts.
One encrypted file
All state lives in a single file, DATA_DIR/state.enc:
- AES-256-GCM with random nonces and version-specific additional authenticated data
- Key supplied as
TOKEN_ENCRYPTION_KEY(for example fromopenssl rand -base64 32). Lose it and the store is unreadable - Writes go to a temp file,
fsync, then an atomic rename - If the directory
fsyncfails, the store is poisoned until restart, rather than continuing in an uncertain state - A
flockprevents a second process from opening the same store
The trade-off: the whole file is re-encrypted on every mutation, and flock means no native Windows storage. That's fine for a single instance and wrong for a fleet, which is why Postgres is the next milestone.
Refresh rotation without races
Entra can rotate refresh tokens, so a naive implementation can lose the only valid one:
- Tokens refresh when less than 90 seconds remain
- Refreshes are serialised per connection
- A new refresh token replaces the old one. If the response omits one, the existing one is preserved
- Revocation takes the same connection lock as refresh, so an in-flight refresh can't resurrect a token the user just deleted
- Store callbacks make no network calls, so a slow upstream can't hold the store lock
Logs that can't leak
Audit logging records only the client ID, operation, outcome and delegation ID. It never contains tokens, request bodies, reference IDs, query strings or raw upstream errors.
Even debug logging is restricted to fixed categories. For example, a malformed pickup is reported as format_shape=string_non_json instead of echoing the value. When debugging an identity flow, it's tempting to log the payload. This design makes that impossible by default.
The portal's browser-facing defences
The portal is a Go HTTPS backend with an embedded Next.js UI:
- The PF token stays server-side. The browser gets an opaque Secure, HttpOnly cookie
- CSRF and
Originchecks on state-changing requests -
TargetResourceis matched exactly on the Reference ID callback - The Form POST from PF ends on a same-origin continuation page. A cross-origin POST redirect would otherwise run into the CSP
form-action 'self'rule
Tests that target the dangerous paths
The broker test suite includes a full mocked lifecycle (TestCompleteLifecycle) and cases for replay, scope escalation, tamper detection, concurrency and credential-bearing redirects. The project runs go test -race, go vet and a build in GitHub Actions.
go test -race -count=1 ./...
go test -v ./internal/broker -run '^TestCompleteLifecycle$'
Known limits
This is not a hardened product. Still open: managed key encryption, key rotation, per-object authorisation policy, rate limiting, and protection against rollback by a privileged filesystem operator.
Last part: running it, deploying it and what comes next.
Top comments (0)