livery_auth_token (livery v0.13.0)

View Source

OAuth 2.0 token endpoint client (RFC 6749 sections 3.2 and 5).

request/3 asks an authorization server for a token with one of three grants: client_credentials (RFC 6749 4.4), authorization_code (4.1, with the PKCE verifier) and refresh_token (6). livery_client:oauth/1 and livery_auth_login use it; call it directly for anything else.

{ok, #{access_token := Token}} = livery_auth_token:request(client_credentials, #{
    scope => <<"orders:read">>
}, #{
    token_endpoint => <<"https://issuer.example/oauth/token">>,
    client_id      => <<"billing">>,
    client_secret  => <<"s3cret">>
}).

Client authentication, client_auth:

  • client_secret_basic (default): HTTP Basic, the id and secret form-encoded first (RFC 6749 2.3.1);
  • client_secret_post: id and secret in the form body;
  • private_key_jwt: a signed assertion (RFC 7523 2.2 as updated by RFC 10070), with private_key => {Alg, Key} and an optional key_id. Its aud is the server's issuer identifier, given as issuer, and nothing else; its typ is client-authentication+jwt;
  • tls_client_auth: mutual TLS (RFC 8705 2); put the certificate in adapter_opts, only client_id goes in the body;
  • none: a public client, client_id in the body.

Grant parameters: scope (binary or list), resource (RFC 8707, one or a list), code, redirect_uri, code_verifier, refresh_token, and extra for anything else. The request goes through a plain livery_client built from adapter and adapter_opts.

The result maps access_token, token_type, expires_at (Unix seconds, from expires_in, or undefined), scope, refresh_token, id_token when present, and raw with the whole response. An error body (RFC 6749 5.2) gives {error, {oauth_error, Code, Description}}.

With dpop => Ctx (a livery_client_dpop context), every request carries a DPoP proof (RFC 9449 section 5), so the server binds the token to that key and answers token_type DPoP; a use_dpop_nonce answer is retried once with the nonce the server sent (section 8). Pushed authorization requests carry the proof too.

Deviations: assertion_audience => token_endpoint puts the token endpoint in the assertion's aud, which RFC 10070 no longer allows, for servers that still expect it. See AGENTS.md, Known deviations.

Summary

Functions

Push an authorization request to Endpoint (RFC 9126), with the same client authentication as token requests, and return the request_uri to send the browser with. Params are the authorization request parameters; a request_uri among them is dropped, as section 2.1 requires. expires_in is in seconds.

Request a token with Grant and its Params.

Types

error_reason()

-type error_reason() ::
          {oauth_error, binary(), binary() | undefined} |
          {http_status, non_neg_integer()} |
          invalid_response |
          {missing_option, atom()} |
          term().

opts()

-type opts() ::
          #{token_endpoint := binary(),
            client_id := binary(),
            client_auth =>
                client_secret_basic | client_secret_post | private_key_jwt | tls_client_auth | none,
            client_secret => binary(),
            private_key => {binary(), term()},
            key_id => binary(),
            issuer => binary(),
            assertion_audience => issuer | token_endpoint,
            adapter => module(),
            adapter_opts => map(),
            timeout => timeout(),
            dpop => livery_client_dpop:ctx(),
            now => integer()}.

token()

-type token() ::
          #{access_token := binary(),
            token_type := binary(),
            expires_at := integer() | undefined,
            scope => binary(),
            refresh_token => binary(),
            id_token => binary(),
            raw := map()}.

Functions

push_request(Endpoint, Params, Opts)

-spec push_request(binary(), [{binary(), binary()}], opts()) ->
                      {ok, #{request_uri := binary(), expires_in := integer() | undefined}} |
                      {error, error_reason()}.

Push an authorization request to Endpoint (RFC 9126), with the same client authentication as token requests, and return the request_uri to send the browser with. Params are the authorization request parameters; a request_uri among them is dropped, as section 2.1 requires. expires_in is in seconds.

request(Grant, Params, Opts)

-spec request(client_credentials | authorization_code | refresh_token, map(), opts()) ->
                 {ok, token()} | {error, error_reason()}.

Request a token with Grant and its Params.