RFC 7636 - Proof Key for Code Exchange (PKCE).
PKCE binds an authorization code to a secret the client generates per
request, so a stolen code is useless without the matching secret. At the
authorization request the client sends a code_challenge (a transform
of a freshly generated code_verifier); at the token request it sends
the code_verifier, and the server recomputes the challenge and
compares.
S256 only
This module implements the S256 method exclusively:
code_challenge = base64url(SHA-256(code_verifier)), no paddingThe plain method (RFC 7636 §4.2, where the challenge is the
verifier) is deliberately not supported: it offers no protection
against an attacker who can read the authorization request, and modern
guidance (OAuth 2.0 Security BCP) requires S256. verify/3 rejects any
method other than "S256" with {:error, :unsupported_method}, so a
downgrade to plain cannot succeed.
Verifier and challenge shapes
- A
code_verifieris 43 to 128 characters from the unreserved set[A-Za-z0-9-._~](RFC 7636 §4.1). - An
S256code_challengeis the canonical 43-character base64url-no-pad encoding of a 32-byte SHA-256 digest - the same shapeAttesto.Thumbprintvalidates.
The comparison at the token endpoint is constant-time
(Attesto.SecureCompare).
Summary
Functions
Compute the S256 code_challenge for a code_verifier.
The only supported code-challenge method, "S256".
Returns true iff value is a well-formed S256 code_challenge:
the canonical 43-character base64url-no-pad encoding of a 32-byte
SHA-256 digest. Delegates to Attesto.Thumbprint.valid?/1.
Returns true iff value is a well-formed code_verifier: 43 to 128
characters drawn from the RFC 7636 §4.1 unreserved set
[A-Za-z0-9-._~].
Verify a presented code_verifier against the stored code_challenge.
Functions
Compute the S256 code_challenge for a code_verifier.
Returns {:ok, challenge} for a well-formed verifier (43-128 unreserved
characters) or {:error, :invalid_verifier} otherwise. The challenge is
base64url(SHA-256(verifier)) without padding.
@spec method() :: String.t()
The only supported code-challenge method, "S256".
Returns true iff value is a well-formed S256 code_challenge:
the canonical 43-character base64url-no-pad encoding of a 32-byte
SHA-256 digest. Delegates to Attesto.Thumbprint.valid?/1.
Returns true iff value is a well-formed code_verifier: 43 to 128
characters drawn from the RFC 7636 §4.1 unreserved set
[A-Za-z0-9-._~].
@spec verify(String.t(), String.t(), String.t()) :: :ok | {:error, :unsupported_method | :invalid_verifier | :invalid_challenge | :mismatch}
Verify a presented code_verifier against the stored code_challenge.
method defaults to "S256" and MUST be "S256"; any other value
(including "plain") returns {:error, :unsupported_method}.
Returns:
:okif the verifier is well-formed and itsS256challenge matchescode_challenge(constant-time compare).{:error, :unsupported_method}ifmethodis not"S256".{:error, :invalid_verifier}if the verifier is not 43-128 unreserved characters.{:error, :invalid_challenge}if the stored challenge is not a canonical 43-character base64url SHA-256 value (it could never have been produced bychallenge/1, so a match is impossible and the stored value is corrupt).{:error, :mismatch}if a well-formed verifier does not match a well-formed challenge.