Private Key JWT Host Guide

Copy Markdown

Lockspire supports a narrow private_key_jwt client-authentication slice for confidential clients on Lockspire-owned direct-client endpoints. This guide explains the shipped surface for Phoenix hosts without widening Lockspire into a general remote-metadata or federation product.

For the canonical public support contract, see docs/supported-surface.md.

What this guide covers

This guide is limited to:

  • confidential clients using token_endpoint_auth_method=private_key_jwt
  • client key material supplied as inline jwks or remote jwks_uri
  • Lockspire-owned direct-client endpoints that reuse the shared verifier
  • rotation behavior for client keys served from jwks_uri

It does not add support for client_secret_jwt, mTLS, generic external trust chains, or federation-style metadata ingestion.

Registration shape

Lockspire accepts private_key_jwt only for confidential clients inside the shipped direct-client slice.

The registration shape is:

  • token_endpoint_auth_method=private_key_jwt
  • exactly one of jwks or jwks_uri
  • signing keys and algorithms that match the effective issuer security posture

jwks and jwks_uri are mutually exclusive. Lockspire rejects registrations or updates that try to set both.

Inline jwks versus remote jwks_uri

Inline jwks keeps the client's public verification keys inside the client record.

jwks_uri is supported through a guarded Lockspire fetch path:

  • https only
  • no redirects
  • unsafe targets fail closed before request dispatch
  • oversized bodies are rejected
  • fetch failures stay generic at the wire boundary as invalid_client

This is a narrow key-retrieval path for client authentication only. It is not a generic outbound metadata-ingestion feature.

Bounded reactive rollover support

Lockspire supports bounded reactive remote-jwks_uri rollover on the shipped remote-key surfaces. That means:

  • successful remote JWKS material is cached for bounded reuse
  • verification can force one refresh when the cached set looks stale or the requested key is unknown
  • refresh failure preserves the last known good cached entry
  • the current authentication attempt still fails closed

Lockspire does not claim proactive rotation readiness. There is no background polling, no prefetch, and no broader remote metadata management subsystem behind this slice.

Assertion requirements

Client assertions must be signed. Lockspire does not allow alg=none.

The assertion must use:

  • iss equal to the client identifier
  • sub equal to the client identifier
  • aud equal to the issuer identifier string
  • bounded lifetime claims with normal skew handling
  • a unique jti so replay protection can record successful use

Lockspire intentionally binds aud to the issuer string rather than accepting endpoint-specific audiences. That is a deliberate security choice for this slice, not an accidental implementation detail.

Accepted signing algorithms follow the current issuer posture:

  • default posture publishes and accepts RS256, ES256, PS256, and EdDSA
  • FAPI 2.0 posture narrows the allowlist to ES256 and PS256

Key rotation behavior

For jwks_uri clients, Lockspire caches successful fetches with a bounded TTL.

When a client rotates keys:

  1. Lockspire keeps using the cached key set until cache expiry or a verification miss requires refresh.
  2. One bounded forced refresh path attempts to load the newer JWKS document.
  3. If refresh succeeds, the new key material replaces the cached entry.
  4. If refresh fails, Lockspire preserves the last known good cache entry and still fails the current authentication attempt closed.

This gives clients a realistic rotation path without turning the embedded library into a broad remote-key management system.

For zero-surprise rollover, publish the new key before first use and keep the previous key available during the overlap window until Lockspire has had a chance to refresh and verify against the new set.

Diagnose remote jwks_uri incidents

Use Lockspire's runtime support surfaces when a jwks_uri client starts failing:

  • mix lockspire.doctor remote-jwks --client <client_id> gives the canonical runtime diagnosis for one client
  • the admin client detail screen renders the same shared Remote JWKS summary

These surfaces normalize incidents into four stable classes:

  • remote_jwks_fetch_failed
  • remote_jwks_invalid
  • remote_jwks_key_unavailable
  • remote_jwks_signature_invalid

mix lockspire.verify is not the right tool for this problem. It checks install and host-wiring prerequisites, not runtime remote-key incidents.

Remediation sequence

When a remote-jwks_uri client fails, follow this sequence:

  1. Classify the incident with mix lockspire.doctor remote-jwks --client <client_id> or the admin Remote JWKS summary.
  2. Check remote reachability and target safety first when the incident is remote_jwks_fetch_failed.
  3. Check the remote JWKS document shape and key metadata when the incident is remote_jwks_invalid.
  4. Confirm overlap-based rollover when the incident is remote_jwks_key_unavailable: publish the new key before first use and keep the previous key present during the transition.
  5. Confirm the client is signing with the intended private key and algorithm when the incident is remote_jwks_signature_invalid.
  6. After correcting the remote state, allow cache and forced-refresh convergence, then retry with one fresh assertion.
  7. Move to inline jwks only when the client cannot operate a reliable overlap-based jwks_uri path or when deterministic cutover is a hard requirement.

Inline jwks is a deliberate fallback, not the default fix for every remote-key incident.

Ownership split

Lockspire owns:

  • the guarded fetch, cache, refresh, and verification path
  • generic fail-closed OAuth wire behavior
  • truthful runtime diagnostics and remediation hints on the shipped support surfaces

The host team owns:

  • reading the diagnostics
  • confirming network reachability, DNS, TLS, or deployment issues on the Lockspire side
  • coordinating incident response and retry timing

The client integrator owns:

  • serving a valid JWKS document over stable HTTPS
  • publishing distinct kid values
  • overlap-based rollover choreography
  • keeping old and new keys available during the transition window

Direct-client endpoints that consume the shared verifier

The shipped private_key_jwt verifier is shared across Lockspire-owned direct-client surfaces, including:

  • POST /token
  • POST /revoke
  • POST /introspect
  • POST /device/code
  • POST /backchannel/authentication

Lockspire documentation and discovery metadata only claim support where the shared runtime actually enforces it.

What hosts still own

Your host app still owns:

  • account records
  • login UX
  • branding and layouts
  • consent and product policy
  • rate limiting and perimeter controls around the mounted Lockspire routes

Lockspire owns the protocol verification path described here. The host app owns the human-facing application behavior around it.