AttestoPhoenix.ClientIdMetadata.Cache behaviour (AttestoPhoenix v0.9.4)

Copy Markdown View Source

Behaviour for caching a validated Client ID Metadata Document - CIMD (draft-ietf-oauth-client-id-metadata-document-01, IETF OAuth WG).

CIMD lets a client identify itself with no prior registration by using an HTTPS URL as its client_id; the authorization server dereferences that URL (AttestoPhoenix.ClientIdMetadata.Fetcher) and validates the returned document (Attesto.ClientIdMetadata.validate_document/2). This behaviour is the seam through which the resolver remembers a successfully validated document so that not every authorization request reaches out to the network.

What may be cached

Only a document that has passed validation is stored, and only with an expires_at the resolver derives from the response's HTTP freshness directives (Cache-Control: max-age / Expires, RFC 9111), clamped to the host's configured bounds. The draft (§6) and RFC 9111 forbid caching error responses or invalid/malformed documents, so an implementation of this behaviour is only ever handed metadata the caller already accepted; it does no validation of its own.

Cache key and value

The key is the CIMD client_id URL (the same string the client presented and the document's client_id equals). The value is the validated, string-keyed metadata map together with its expires_at. get/1 MUST treat an expired entry as a miss - freshness is re-checked on read, never honored past expires_at - so an implementation that cannot cheaply evict still cannot serve a stale document.

Default and the opt-out

The default implementation is AttestoPhoenix.ClientIdMetadata.Cache.Ecto, which persists the entry to Postgres (table attesto_client_id_metadata, swept by AttestoPhoenix.Store.Sweeper) so the cache is coherent across a cluster and the outbound fetch fan-out is bounded under load. A single-node deployment may opt into the per-node AttestoPhoenix.ClientIdMetadata.Cache.ETS instead - a per-node cache is correct here because a miss simply re-fetches - by configuring the :cache module under AttestoPhoenix.Config's :client_id_metadata key.

Summary

Types

A validated, string-keyed CIMD metadata map - the document Attesto.ClientIdMetadata.validate_document/2 returned and the caller accepted. Only such a map is ever stored or returned.

Callbacks

Looks up the cached metadata for a CIMD client_id URL.

Stores validated metadata for a CIMD client_id URL until expires_at.

Types

metadata()

@type metadata() :: map()

A validated, string-keyed CIMD metadata map - the document Attesto.ClientIdMetadata.validate_document/2 returned and the caller accepted. Only such a map is ever stored or returned.

Callbacks

get(url)

@callback get(url :: String.t()) :: {:ok, metadata()} | :miss

Looks up the cached metadata for a CIMD client_id URL.

Returns {:ok, metadata} only for an entry that is present AND still fresh (expires_at strictly in the future); an absent or expired entry is a :miss. Expiry MUST be re-checked here, so an implementation never serves a document past the expires_at it was stored with - an unswept expired row is a miss, not a stale hit.

put(url, metadata, expires_at)

@callback put(url :: String.t(), metadata :: metadata(), expires_at :: DateTime.t()) ::
  :ok

Stores validated metadata for a CIMD client_id URL until expires_at.

The caller passes this only after Attesto.ClientIdMetadata.validate_document/2 succeeds and after deriving expires_at from the response's HTTP freshness directives clamped to the configured bounds; an implementation MUST NOT be asked to cache an error or an invalid document (draft §6 / RFC 9111). A re-fetched document legitimately supersedes a stale one, so put/3 replaces any existing entry for the same url rather than failing on conflict.