MPP.Challenge (mpp v0.6.2)

Copy Markdown View Source

Payment challenge — the 402 response that tells a client what to pay.

A challenge is returned in the WWW-Authenticate: Payment header when a request lacks a valid payment credential. The challenge ID is HMAC-SHA256 bound to all challenge parameters, making it tamper-proof without requiring server-side state.

HMAC Binding

The challenge ID is computed as:

base64url(HMAC-SHA256(secret_key, realm|method|intent|request|expires|digest|opaque))

Seven fixed positional slots joined by |. Optional fields use empty string when absent, preserving slot positions. The request and opaque fields are used as their raw base64url-encoded strings (never re-serialized).

Fields

  • id — HMAC-SHA256 challenge ID (computed by create/2)
  • realm — server protection space (e.g., "api.example.com")
  • method — payment method name (e.g., "stripe", "tempo")
  • intent — intent type (e.g., "charge")
  • request — base64url-encoded JSON request payload (pre-encoded)
  • description — (optional) human-readable description
  • digest — (optional) content digest per RFC 9530
  • expires — (optional) RFC 3339 expiration timestamp
  • opaque — (optional) base64url-encoded JSON server correlation data

API Functions

FunctionArityDescriptionParam Kinds
verify_server_binding3Verify challenge HMAC using the server's configured realm (Tier-1 server binding). Recomputes the ID with server_realm instead of the echoed realm, matching mpp-rs verify_hmac_and_expiry.challenge: value, secret_key: value, server_realm: value
verify2Verify a challenge's HMAC-bound ID against a secret key using constant-time comparison.challenge: value, secret_key: value
create2Create a new challenge with an HMAC-SHA256 bound ID.params: value, secret_key: value

Summary

Functions

Create a new challenge with an HMAC-SHA256 bound ID.

Verify a challenge's HMAC-bound ID against a secret key using constant-time comparison.

Verify challenge HMAC using the server's configured realm (Tier-1 server binding). Recomputes the ID with server_realm instead of the echoed realm, matching mpp-rs verify_hmac_and_expiry.

Types

t()

@type t() :: %MPP.Challenge{
  description: String.t() | nil,
  digest: String.t() | nil,
  expires: String.t() | nil,
  id: String.t() | nil,
  intent: String.t(),
  method: String.t(),
  opaque: String.t() | nil,
  realm: String.t(),
  request: String.t()
}

Functions

create(params, secret_key)

@spec create(
  keyword(),
  String.t()
) :: t()

Create a new challenge with an HMAC-SHA256 bound ID.

Parameters

  • params - Keyword list with :realm, :method, :intent, :request (required) and :description, :digest, :expires, :opaque (optional) (value)
  • secret_key - HMAC-SHA256 secret key for challenge binding (value)

Returns

Challenge struct with computed id (struct)

Composes With

  • verify
# descripex:contract
%{
  params: %{
    params: %{
      description: "Keyword list with `:realm`, `:method`, `:intent`, `:request` (required) and `:description`, `:digest`, `:expires`, `:opaque` (optional)",
      kind: :value
    },
    secret_key: %{
      description: "HMAC-SHA256 secret key for challenge binding",
      kind: :value
    }
  },
  returns: %{type: :struct, description: "Challenge struct with computed `id`"},
  composes_with: [:verify]
}

verify(challenge, secret_key)

@spec verify(t(), String.t()) :: :ok | {:error, :invalid_challenge}
@spec verify(t(), String.t()) :: {:error, :invalid_challenge}

Verify a challenge's HMAC-bound ID against a secret key using constant-time comparison.

Parameters

  • challenge - Challenge struct to verify (value)
  • secret_key - HMAC-SHA256 secret key used when challenge was created (value)

Returns

:ok if valid, {:error, :invalid_challenge} if tampered (tagged)

Errors

  • :invalid_challenge

Composes With

  • create
# descripex:contract
%{
  params: %{
    secret_key: %{
      description: "HMAC-SHA256 secret key used when challenge was created",
      kind: :value
    },
    challenge: %{description: "Challenge struct to verify", kind: :value}
  },
  errors: [:invalid_challenge],
  returns: %{
    type: :tagged,
    description: "`:ok` if valid, `{:error, :invalid_challenge}` if tampered"
  },
  composes_with: [:create]
}

verify_server_binding(challenge, secret_key, server_realm)

@spec verify_server_binding(t(), String.t(), String.t()) ::
  :ok | {:error, :invalid_challenge}
@spec verify_server_binding(t(), String.t(), String.t()) ::
  {:error, :invalid_challenge}

Verify challenge HMAC using the server's configured realm (Tier-1 server binding). Recomputes the ID with server_realm instead of the echoed realm, matching mpp-rs verify_hmac_and_expiry.

Parameters

  • challenge - Challenge struct with echoed fields (value)
  • secret_key - HMAC-SHA256 secret key (value)
  • server_realm - Server's configured protection space realm (value)

Returns

:ok if valid, {:error, :invalid_challenge} if tampered (tagged)

Errors

  • :invalid_challenge

Composes With

  • create
  • verify
# descripex:contract
%{
  params: %{
    secret_key: %{description: "HMAC-SHA256 secret key", kind: :value},
    challenge: %{
      description: "Challenge struct with echoed fields",
      kind: :value
    },
    server_realm: %{
      description: "Server's configured protection space realm",
      kind: :value
    }
  },
  errors: [:invalid_challenge],
  returns: %{
    type: :tagged,
    description: "`:ok` if valid, `{:error, :invalid_challenge}` if tampered"
  },
  composes_with: [:create, :verify]
}