DEV Community

DarkEdges
DarkEdges

Posted on

Securing the Token Vault: Encrypted State, Refresh Rotation and Safe Logs

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 from openssl rand -base64 32). Lose it and the store is unreadable
  • Writes go to a temp file, fsync, then an atomic rename
  • If the directory fsync fails, the store is poisoned until restart, rather than continuing in an uncertain state
  • A flock prevents 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 Origin checks on state-changing requests
  • TargetResource is 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$'
Enter fullscreen mode Exit fullscreen mode

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)