ConduitMcp.Client.OAuth.PKCE (ConduitMCP v0.11.0)

Copy Markdown View Source

Proof Key for Code Exchange (RFC 7636), S256 only.

The client never sends the plain method: an authorization server that does not advertise "S256" in code_challenge_methods_supported (or omits the field) is refused before any authorization or token request (OA-1).

iex> ConduitMcp.Client.OAuth.PKCE.challenge("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk")
"E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"

Summary

Functions

The S256 code challenge for verifier (a string or a Secret wrapping one): BASE64URL(SHA256(ASCII(verifier))) without padding (RFC 7636 §4.2).

Returns :ok when s256_supported?/1 holds, else raises a ConduitMcp.Client.OAuth.Error with reason :pkce_unsupported.

The only code_challenge_method the client sends: "S256".

Whether decoded authorization-server metadata lists "S256" in code_challenge_methods_supported. The method name is compared exactly (it is case-sensitive); a missing or malformed field means false.

Whether verifier is a syntactically valid RFC 7636 §4.1 code verifier.

A fresh code verifier: 32 bytes from :crypto.strong_rand_bytes/1, base64url-encoded without padding (43 characters, RFC 7636 §4.1).

Functions

challenge(verifier)

@spec challenge(String.t() | ConduitMcp.Client.Secret.t()) :: String.t()

The S256 code challenge for verifier (a string or a Secret wrapping one): BASE64URL(SHA256(ASCII(verifier))) without padding (RFC 7636 §4.2).

Raises ArgumentError unless verifier is 43–128 characters of [A-Za-z0-9-._~] (RFC 7636 §4.1).

ensure_s256!(metadata)

@spec ensure_s256!(map()) :: :ok

Returns :ok when s256_supported?/1 holds, else raises a ConduitMcp.Client.OAuth.Error with reason :pkce_unsupported.

method()

@spec method() :: String.t()

The only code_challenge_method the client sends: "S256".

s256_supported?(arg1)

@spec s256_supported?(map()) :: boolean()

Whether decoded authorization-server metadata lists "S256" in code_challenge_methods_supported. The method name is compared exactly (it is case-sensitive); a missing or malformed field means false.

iex> ConduitMcp.Client.OAuth.PKCE.s256_supported?(%{"code_challenge_methods_supported" => ["plain", "S256"]})
true
iex> ConduitMcp.Client.OAuth.PKCE.s256_supported?(%{"code_challenge_methods_supported" => ["plain"]})
false
iex> ConduitMcp.Client.OAuth.PKCE.s256_supported?(%{})
false

valid_verifier?(verifier)

@spec valid_verifier?(term()) :: boolean()

Whether verifier is a syntactically valid RFC 7636 §4.1 code verifier.

verifier()

@spec verifier() :: String.t()

A fresh code verifier: 32 bytes from :crypto.strong_rand_bytes/1, base64url-encoded without padding (43 characters, RFC 7636 §4.1).

The verifier is a secret; wrap it in ConduitMcp.Client.Secret wherever it is held (as ConduitMcp.Client.OAuth.Pending does).