livery_auth (livery v0.13.0)

View Source

JWT verification against a JWK set.

Verifies compact-serialization JSON Web Tokens signed with any of the asymmetric algorithms in livery_auth_jose (RSA, RSA-PSS, ECDSA and EdDSA), then validates the registered claims. Signature checks use the OTP public_key and crypto modules; no third-party crypto is pulled in. The JWK set is supplied by the caller (see livery_auth_jwks to fetch one), so verification itself never touches the network.

{ok, Claims} = livery_auth:verify(Token, #{
    keys     => JwkList,
    issuer   => <<"https://issuer.example">>,
    audience => <<"my-api">>
}).

Options:

  • keys (required): the JWK maps to verify against.
  • issuer, audience: expected iss, and one or a list of accepted aud values.
  • algorithms: accepted alg values, default livery_auth_jose:algorithms/0.
  • typ: required header typ, one value or a list, compared without case and with the application/ prefix optional (RFC 7515 4.1.9).
  • required_claims: claim names that must be present.
  • scope: scopes the token must grant, read from scope (space separated) or scp (a list).
  • profile => access_token: the RFC 9068 profile. Requires typ at+jwt, the claims iss, exp, aud, sub, client_id, iat and jti, and the issuer and audience options.
  • now, leeway: clock and allowed skew, in seconds.

A token with a crit header is refused, since no JWS extension is understood (RFC 7515 4.1.11). exp, nbf and iat may be any JSON number (RFC 7519 2).

Deviations: typ is not required unless asked for, although RFC 8725 3.11 recommends explicit typing, because most issuers send JWT or nothing. scp is read as well as scope, because Azure AD and Okta issue it. See AGENTS.md, Known deviations.

Summary

Functions

Whether an aud value (a string or a list) contains one of Expected (one value or a list).

Whether verified Claims may proceed: the sign-in is recent and strong enough (RFC 9470, livery_auth_stepup), then the token carries every required scope. Shared by livery_auth_bearer and livery_auth_introspect, in that order.

The outbound client the auth modules use to reach an authorization server (key sets, discovery, introspection): it verifies the server's certificate with tls_opts/0 and gives up after 5 seconds without a connection.

Scopes from Required that Claims does not grant, read from scope (space separated, RFC 9068 2.2.3 and RFC 7662 2.2) or scp (a list).

TLS client options for verifying an HTTPS peer's certificate.

Verify a JWT and return its validated claims.

Types

claims()

-type claims() :: #{binary() => term()}.

error_reason()

-type error_reason() ::
          malformed | invalid_json |
          {unsupported_alg, term()} |
          {unsupported_crit, term()} |
          {invalid_typ, term()} |
          no_matching_key | bad_signature | expired | not_yet_valid |
          {invalid_claim, binary()} |
          {missing_claim, binary()} |
          {missing_option, issuer | audience} |
          {issuer_mismatch, binary()} |
          audience_mismatch |
          {insufficient_scope, [binary()]}.

jwk()

-type jwk() :: #{binary() => term()}.

verify_opts()

-type verify_opts() ::
          #{keys := [jwk()],
            issuer => binary() | undefined,
            audience => binary() | [binary()] | undefined,
            algorithms => [binary()],
            typ => binary() | [binary()],
            required_claims => [binary()],
            scope => [binary()],
            profile => access_token,
            now => number(),
            leeway => non_neg_integer()}.

Functions

audience_matches/2

-spec audience_matches(term(), binary() | [binary()]) -> boolean().

Whether an aud value (a string or a list) contains one of Expected (one value or a list).

authorized(Claims, State)

-spec authorized(claims(), map()) -> ok | {error, {insufficient_scope, [binary()]} | term()}.

Whether verified Claims may proceed: the sign-in is recent and strong enough (RFC 9470, livery_auth_stepup), then the token carries every required scope. Shared by livery_auth_bearer and livery_auth_introspect, in that order.

client()

-spec client() -> livery_client:client().

The outbound client the auth modules use to reach an authorization server (key sets, discovery, introspection): it verifies the server's certificate with tls_opts/0 and gives up after 5 seconds without a connection.

missing_scopes(Claims, Required)

-spec missing_scopes(map(), [binary()]) -> [binary()].

Scopes from Required that Claims does not grant, read from scope (space separated, RFC 9068 2.2.3 and RFC 7662 2.2) or scp (a list).

tls_opts()

-spec tls_opts() -> [ssl:tls_client_option()].

TLS client options for verifying an HTTPS peer's certificate.

Used by the OIDC/JWKS/introspection fetchers so the channel that discovers signing keys (and thus the identity trust root) is authenticated: a forged JWK set served by an on-path attacker would otherwise let them mint tokens this node accepts. Verifies against the OS trust store with hostname checking.

verify(Token, Opts0)

-spec verify(binary(), verify_opts()) -> {ok, claims()} | {error, error_reason()}.

Verify a JWT and return its validated claims.

Steps: split the compact token, check the header (crit, typ, alg), find a fitting key and verify the signature, then validate the claims.