Lockspire.Protocol.AccessTokenSigner (lockspire v1.3.0)

Copy Markdown

Shared access-token issuance for all Lockspire grant paths.

Owns the single RFC 9068 at+jwt signing site, the opaque-token delegate, and the one-place format decision (per-client override -> server default -> :jwt).

Format resolution

The effective access-token format is resolved in exactly one place (resolve_format/2):

  1. a per-client access_token_format of :jwt or :opaque wins;
  2. otherwise (nil) the server-wide ServerPolicy.access_token_format read via request.opts[:server_policy_store] is used;
  3. otherwise it falls back to :jwt.

Audience derivation

The four grant paths (authorization-code, refresh, device-code, CIBA) emit a LIST aud: the requested resource(s) when present, otherwise [client_id]. The RFC 8693 token-exchange path keeps a BARE-STRING aud == client_id via issue_exchange/4, preserving the historical exchange wire shape.

Security

The signing alg/kid are taken ONLY from the active signing key — never from client-controlled input — and none is never emitted. On a missing or invalid key the error path logs inspect(reason) only: no key material reaches logs.

Summary

Functions

Issue an access token for a standard grant path.

Issue a signed at+jwt access token for the RFC 8693 token-exchange path.

Types

result()

@type result() ::
  {:ok, String.t(), String.t()}
  | {:error, Lockspire.Protocol.TokenExchange.Error.t()}

Functions

issue(token, client, request)

Issue an access token for a standard grant path.

Resolves the effective format and returns {:ok, raw, hash} where hash == Lockspire.Security.Policy.hash_token(raw), or a 500 :token_signing_failed error when the :jwt branch cannot sign.

The :jwt branch emits a LIST aud derived from token.audience.

issue_exchange(token, client, custom_claims, request)

@spec issue_exchange(
  Lockspire.Domain.Token.t(),
  Lockspire.Domain.Client.t(),
  map(),
  map()
) :: result()

Issue a signed at+jwt access token for the RFC 8693 token-exchange path.

Always :jwt. Emits a BARE-STRING aud == client.client_id (the exchange carve-out) and merges custom_claims over the base claims after dropping the restricted claims iss sub aud exp iat jti client_id.