General-purpose JWT module wrapping JOSE for MCP authorization.
Provides key loading, JWT signing, verification, and claims validation used by OAuth client assertions, ID-JAG tokens, and JWT bearer grants.
Summary
Functions
Fetches a JWKS (JSON Web Key Set) from a URL.
Generates an EC key pair for development/testing.
Generates a unique JWT ID (jti).
Generates an RSA key pair for development/testing.
Loads a JWK from a PEM string, JWK map, or file path.
Reads the unverified header from a JWS token (for typ checking).
Signs a claims map into a compact JWS string.
Converts a JWK to a map representation (for JWKS publishing).
Extracts the public key from a JWK.
Validates standard JWT claims against expected values.
Verifies the signature of a JWS string and returns the decoded claims.
Verifies a JWS string and validates claims against expected values.
Functions
@spec fetch_jwks( String.t(), keyword() ) :: {:ok, [JOSE.JWK.t()]} | {:error, term()}
Fetches a JWKS (JSON Web Key Set) from a URL.
Uses the same HTTPS-only, public-address, pinned and bounded fetch policy as
authorization-server and client metadata discovery. Custom HTTP clients must
implement get(uri, approved_address, opts).
@spec generate_ec_key(keyword()) :: JOSE.JWK.t()
Generates an EC key pair for development/testing.
Options
:curve- EC curve name (default: "P-256")
@spec generate_jti() :: String.t()
Generates a unique JWT ID (jti).
@spec generate_rsa_key(keyword()) :: JOSE.JWK.t()
Generates an RSA key pair for development/testing.
Options
:size- Key size in bits (default: 2048)
@spec load_key(map() | String.t() | {:pem_file, String.t()}) :: {:ok, JOSE.JWK.t()} | {:error, term()}
Loads a JWK from a PEM string, JWK map, or file path.
Examples
{:ok, jwk} = JWT.load_key(%{"kty" => "RSA", ...})
{:ok, jwk} = JWT.load_key("-----BEGIN RSA PRIVATE KEY-----\n...")
{:ok, jwk} = JWT.load_key({:pem_file, "/path/to/key.pem"})
Reads the unverified header from a JWS token (for typ checking).
@spec sign(map(), JOSE.JWK.t(), keyword()) :: {:ok, String.t()} | {:error, term()}
Signs a claims map into a compact JWS string.
Options
:alg- Signing algorithm (default: "RS256"):kid- Key ID to include in header:typ- Token type header (default: "JWT")
@spec to_map(JOSE.JWK.t()) :: map()
Converts a JWK to a map representation (for JWKS publishing).
@spec to_public_key(JOSE.JWK.t()) :: JOSE.JWK.t()
Extracts the public key from a JWK.
Validates standard JWT claims against expected values.
Time-based claims
exp is required by default and must be a number: a token without an
expiry, or with a non-numeric expiry, is rejected. nbf and iat are
optional, but when present they must be numeric — a non-numeric value is
rejected rather than ignored.
All time comparisons allow :leeway seconds of clock skew (default
30).
Options
:leeway- Clock skew allowance in seconds (default:30):require_exp- Require anexpclaim (default:true):iss- Expected issuer:aud- Expected audience (string or list):sub- Expected subject:max_age- Maximum token age in seconds:required- List of required claim keys (as strings)
Errors
{:error, :missing_exp}- noexpclaim andrequire_expis true{:error, {:invalid_claim_type, "exp" | "nbf" | "iat"}}- claim present but not a number{:error, :token_expired}/{:error, :token_not_yet_valid}/{:error, :invalid_iat}
@spec verify(String.t(), JOSE.JWK.t() | [JOSE.JWK.t()]) :: {:ok, map()} | {:error, term()}
Verifies the signature of a JWS string and returns the decoded claims.
Accepts a single JWK or a list of JWKs (JWKS).
Signature verification only
This function performs no claims validation: an expired token, a
token that is not yet valid, or a token issued for another audience all
verify successfully as long as the signature is good. Use
verify_and_validate/3 (or validate_claims/2 on the result) for
anything that makes an authorization decision.
@spec verify_and_validate(String.t(), JOSE.JWK.t() | [JOSE.JWK.t()], keyword()) :: {:ok, map()} | {:error, term()}
Verifies a JWS string and validates claims against expected values.
Takes the same options as validate_claims/2. Note that exp is required
by default; pass require_exp: false for the rare token profile that
legitimately omits it.