oidcc_token (Oidcc v3.5.1)

View Source

Facilitate OpenID Code/Token Exchanges.

Records

To use the records, import the definition:

-include_lib(["oidcc/include/oidcc_token.hrl"]).

Telemetry

See Oidcc.Token.

Summary

Types

Access Token Wrapper.

ID Token Wrapper.

Refresh Token Wrapper.

Options for retrieving a token.

t()

Token Response Wrapper.

Options for refreshing a token.

Functions

Retrieve Client Credential Token

Retrieve JSON Web Token (JWT) Profile Token

Refresh Token

Retrieve the token using the authcode received before and directly validate the result.

Validate the JARM response, returning the valid claims as a map.

Types

access()

(since 3.0.0)
-type access() ::
          #oidcc_token_access{token :: binary(), expires :: pos_integer() | undefined, type :: binary()}.

Access Token Wrapper.

Fields

  • token - The retrieved token.
  • expires - Number of seconds the token is valid.

authorization_headers_opts()

(since 3.0.0)
-type authorization_headers_opts() :: #{dpop_nonce => binary()}.

client_credentials_opts()

(since 3.0.0)
-type client_credentials_opts() ::
          #{scope => oidcc_scope:scopes(),
            refresh_jwks => oidcc_jwt_util:refresh_jwks_for_unknown_kid_fun(),
            request_opts => oidcc_http_util:request_opts(),
            url_extension => oidcc_http_util:query_params(),
            body_extension => oidcc_http_util:query_params()}.

error()

(since 3.0.0)
-type error() ::
          {missing_claim, MissingClaim :: binary(), Claims :: oidcc_jwt_util:claims()} |
          pkce_verifier_required | no_supported_auth_method | bad_access_token_hash | sub_invalid |
          token_expired | token_not_yet_valid |
          {none_alg_used, Token :: t()} |
          {missing_claim, ExpClaim :: {binary(), term()}, Claims :: oidcc_jwt_util:claims()} |
          {grant_type_not_supported,
           authorization_code | refresh_token | jwt_bearer | client_credentials} |
          {invalid_property,
           {Field :: id_token | refresh_token | access_token | expires_in | scopes,
            GivenValue :: term()}} |
          no_supported_code_challenge |
          oidcc_jwt_util:error() |
          oidcc_http_util:error().

id()

(since 3.0.0)
-type id() :: #oidcc_token_id{token :: binary(), claims :: oidcc_jwt_util:claims()}.

ID Token Wrapper.

Fields

  • token - The retrieved token.
  • claims - Unpacked claims of the verified token.

jwt_profile_opts()

(since 3.0.0)
-type jwt_profile_opts() ::
          #{scope => oidcc_scope:scopes(),
            refresh_jwks => oidcc_jwt_util:refresh_jwks_for_unknown_kid_fun(),
            request_opts => oidcc_http_util:request_opts(),
            kid => binary(),
            url_extension => oidcc_http_util:query_params(),
            body_extension => oidcc_http_util:query_params()}.

refresh()

(since 3.0.0)
-type refresh() :: #oidcc_token_refresh{token :: binary()}.

Refresh Token Wrapper.

Fields

  • token - The retrieved token.

refresh_opts()

(since 3.0.0)
-type refresh_opts() ::
          #{scope => oidcc_scope:scopes(),
            refresh_jwks => oidcc_jwt_util:refresh_jwks_for_unknown_kid_fun(),
            expected_subject := binary(),
            request_opts => oidcc_http_util:request_opts(),
            url_extension => oidcc_http_util:query_params(),
            body_extension => oidcc_http_util:query_params()}.

refresh_opts_no_sub()

(since 3.0.0)
-type refresh_opts_no_sub() ::
          #{scope => oidcc_scope:scopes(),
            refresh_jwks => oidcc_jwt_util:refresh_jwks_for_unknown_kid_fun(),
            request_opts => oidcc_http_util:request_opts(),
            url_extension => oidcc_http_util:query_params(),
            body_extension => oidcc_http_util:query_params()}.

See refresh_opts_no_sub/0.

retrieve_opts()

(since 3.0.0)
-type retrieve_opts() ::
          #{pkce_verifier => binary(),
            require_pkce => boolean(),
            nonce => binary() | any,
            scope => oidcc_scope:scopes(),
            preferred_auth_methods => [oidcc_auth_util:auth_method(), ...],
            refresh_jwks => oidcc_jwt_util:refresh_jwks_for_unknown_kid_fun(),
            redirect_uri => uri_string:uri_string(),
            request_opts => oidcc_http_util:request_opts(),
            url_extension => oidcc_http_util:query_params(),
            body_extension => oidcc_http_util:query_params(),
            dpop_nonce => binary(),
            trusted_audiences => [binary()] | any,
            token_request_claims => #{binary() => binary() | integer()}}.

Options for retrieving a token.

See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3.

Fields

t()

(since 3.0.0)
-type t() ::
          #oidcc_token{id :: oidcc_token:id() | none,
                       access :: oidcc_token:access() | none,
                       refresh :: oidcc_token:refresh() | none,
                       scope :: oidcc_scope:scopes()}.

Token Response Wrapper.

Fields

validate_jarm_opts()

(since 3.2.0)
-type validate_jarm_opts() :: #{trusted_audiences => [binary()] | any}.

Options for refreshing a token.

See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3.

Fields

validate_jwt_opts()

(since 3.2.0)
-type validate_jwt_opts() ::
          #{signing_algs => [binary()] | undefined,
            encryption_algs => [binary()] | undefined,
            encryption_encs => [binary()] | undefined,
            trusted_audiences => [binary()] | any,
            refresh_jwks => oidcc_jwt_util:refresh_jwks_for_unknown_kid_fun()}.

Functions

authorization_headers(AccessTokenRecord, Method, Endpoint, ClientContext)

(since 3.2.0)
-spec authorization_headers(AccessTokenRecord, Method, Endpoint, ClientContext) -> HeaderMap
                               when
                                   AccessTokenRecord :: access(),
                                   Method :: post | get,
                                   Endpoint :: uri_string:uri_string(),
                                   ClientContext :: oidcc_client_context:t(),
                                   HeaderMap :: #{binary() => binary()}.

Authorization headers

Generate a map of authorization headers to use when using the given access token to access an API endpoint.

Examples

{ok, ClientContext} =
    oidcc_client_context:from_configuration_worker(provider_name,
                                                    <<"client_id">>,
                                                    <<"client_secret">>),
%% Get Access Token record from somewhere
Headers =
    oidcc:authorization_headers(AccessTokenRecord, :get, Url, ClientContext).

authorization_headers(AccessTokenRecord, Method, Endpoint, ClientContext, Opts)

(since 3.0.0)
-spec authorization_headers(AccessTokenRecord, Method, Endpoint, ClientContext, Opts) -> HeaderMap
                               when
                                   AccessTokenRecord :: access(),
                                   Method :: post | get,
                                   Endpoint :: uri_string:uri_string(),
                                   ClientContext :: oidcc_client_context:t(),
                                   Opts :: authorization_headers_opts(),
                                   HeaderMap :: #{binary() => binary()}.

client_credentials(ClientContext, Opts)

(since 3.0.0)
-spec client_credentials(ClientContext, Opts) -> {ok, t()} | {error, error()}
                            when
                                ClientContext :: oidcc_client_context:authenticated_t(),
                                Opts :: client_credentials_opts().

Retrieve Client Credential Token

See https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.4

For a high level interface using oidcc_provider_configuration_worker see oidcc:client_credentials_token/4.

Examples

{ok, ClientContext} =
  oidcc_client_context:from_configuration_worker(provider_name,
                                                 <<"client_id">>,
                                                 <<"client_secret">>),

{ok, #oidcc_token{}} =
  oidcc_token:client_credentials(ClientContext,
                                 #{scope => [<<"scope">>]}).

jwt_profile(Subject, ClientContext, Jwk, Opts)

(since 3.0.0)
-spec jwt_profile(Subject, ClientContext, Jwk, Opts) -> {ok, t()} | {error, error()}
                     when
                         Subject :: binary(),
                         ClientContext :: oidcc_client_context:t(),
                         Jwk :: jose_jwk:key(),
                         Opts :: jwt_profile_opts().

Retrieve JSON Web Token (JWT) Profile Token

See [https://datatracker.ietf.org/doc/html/rfc7523#section-4]

For a high level interface using {@link oidcc_provider_configuration_worker} see {@link oidcc:jwt_profile_token/6}.

Examples

{ok, ClientContext} =
  oidcc_client_context:from_configuration_worker(provider_name,
                                                 <<"client_id">>,
                                                 <<"client_secret">>),

{ok, KeyJson} = file:read_file("jwt-profile.json"),
KeyMap = jose:decode(KeyJson),
Key = jose_jwk:from_pem(maps:get(<<"key">>, KeyMap)),

{ok, #oidcc_token{}} =
  oidcc_token:jwt_profile(<<"subject">>,
                          ClientContext,
                          Key,
                          #{scope => [<<"scope">>],
                            kid => maps:get(<<"keyId">>, KeyMap)}).

refresh/3

(since 3.0.0)
-spec refresh(RefreshToken, ClientContext, Opts) -> {ok, t()} | {error, error()}
                 when
                     RefreshToken :: binary(),
                     ClientContext :: oidcc_client_context:t(),
                     Opts :: refresh_opts();
             (Token, ClientContext, Opts) -> {ok, t()} | {error, error()}
                 when
                     Token :: oidcc_token:t(),
                     ClientContext :: oidcc_client_context:t(),
                     Opts :: refresh_opts_no_sub().

Refresh Token

For a high level interface using oidcc_provider_configuration_worker see oidcc:refresh_token/5.

Examples

{ok, ClientContext} =
  oidcc_client_context:from_configuration_worker(provider_name,
                                                 <<"client_id">>,
                                                 <<"client_secret">>),

%% Get AuthCode from Redirect

{ok, Token} =
  oidcc_token:retrieve(AuthCode, ClientContext, #{
    redirect_uri => <<"https://example.com/callback">>}).

%% Later

{ok, #oidcc_token{}} =
  oidcc_token:refresh(Token,
                      ClientContext,
                      #{expected_subject => <<"sub_from_initial_id_token">>}).

retrieve(AuthCode, ClientContext, Opts)

(since 3.0.0)
-spec retrieve(AuthCode, ClientContext, Opts) -> {ok, t()} | {error, error()}
                  when
                      AuthCode :: binary(),
                      ClientContext :: oidcc_client_context:t(),
                      Opts :: retrieve_opts().

Retrieve the token using the authcode received before and directly validate the result.

The authcode was sent to the local endpoint by the OpenId Connect provider, using redirects.

For a high level interface using oidcc_provider_configuration_worker see oidcc:retrieve_token/5.

Examples

{ok, ClientContext} =
  oidcc_client_context:from_configuration_worker(provider_name,
                                                 <<"client_id">>,
                                                 <<"client_secret">>),

%% Get AuthCode from Redirect

{ok, #oidcc_token{}} =
  oidcc:retrieve(AuthCode, ClientContext, #{
    redirect_uri => <<"https://example.com/callback">>}).

validate_id_token(IdToken, ClientContext, NonceOrOpts)

(since 3.0.0)
-spec validate_id_token(IdToken, ClientContext, NonceOrOpts) -> {ok, Claims} | {error, error()}
                           when
                               IdToken :: binary(),
                               ClientContext :: oidcc_client_context:t(),
                               NonceOrOpts :: Nonce | retrieve_opts(),
                               Nonce :: binary() | any,
                               Claims :: oidcc_jwt_util:claims().

Validate ID Token

Usually the id token is validated using retrieve/3.

If you get the token passed from somewhere else, this function can validate it.

Validations

  • iss claim must match the issuer of the provider, or match the regex pattern if issuer_regex is configured in quirks.
  • nonce claim must match the nonce option.
  • aud claim must match the trusted_audiences option.
  • azp claim must match the client id.
  • exp claim must be in the future.
  • nbf claim must be in the past.

Examples

{ok, ClientContext} =
  oidcc_client_context:from_configuration_worker(provider_name,
                                                 <<"client_id">>,
                                                 <<"client_secret">>),

%% Get IdToken from somewhere

{ok, Claims} =
  oidcc:validate_id_token(IdToken, ClientContext, ExpectedNonce).

Regex Issuer Validation

You can use a regex pattern to validate the issuer claim by adding an issuer_regex to the quirks map when creating the provider configuration. See the documentation for validate_jwt/3 for more details.

validate_jarm(Response, ClientContext, Opts)

(since 3.2.0)
-spec validate_jarm(Response, ClientContext, Opts) -> {ok, oidcc_jwt_util:claims()} | {error, error()}
                       when
                           Response :: binary(),
                           ClientContext :: oidcc_client_context:t(),
                           Opts :: validate_jarm_opts().

Validate the JARM response, returning the valid claims as a map.

The response was sent to the local endpoint by the OpenId Connect provider, using redirects.

Examples

{ok, ClientContext} =
  oidcc_client_context:from_configuration_worker(provider_name,
                                                 <<"client_id">>,
                                                 <<"client_secret">>),

%% Get Response from Redirect

{ok, #{<<"code">> := AuthCode}} =
  oidcc:validate_jarm(Response, ClientContext, #{}),

{ok, #oidcc_token{}} = oidcc:retrieve(AuthCode, ClientContext,
  #{redirect_uri => <<"https://redirect.example/">>}).

validate_jwt(Token, ClientContext, Opts)

(since 3.2.0)
-spec validate_jwt(Token, ClientContext, Opts) -> {ok, Claims} | {error, error()}
                      when
                          Token :: binary(),
                          ClientContext :: oidcc_client_context:t(),
                          Opts :: validate_jwt_opts(),
                          Claims :: oidcc_jwt_util:claims().

Validate JWT

Validates a generic JWT (such as an access token) from the given provider. Useful if the issuer is shared between multiple applications, and the access token generated for a user at one client is used to validate their access at another client.

Validating an arbitrary JWT token (not an ID token) is not covered by the OpenID Connect specification. Therefore the signing / encryption algorithms are not derieved from the provider configuration, but must be provided by the caller.

Validations

  • iss claim must match the issuer of the provider, or match the regex pattern if issuer_regex is configured in quirks.
  • aud claim must match the trusted_audiences option.
  • exp claim must be in the future.
  • nbf claim must be in the past.

Examples

{ok, ClientContext} =
    oidcc_client_context:from_configuration_worker(provider_name,
                                                <<"client_id">>,
                                                <<"client_secret">>),
%% Get Jwt from Authorization header
Jwt = <<"jwt">>,

Opts = #{
  signing_algs => [<<"RS256">>]
},

{ok, Claims} =
    oidcc:validate_jwt(Jwt, ClientContext, Opts).

Regex Issuer Validation

You can use a regex pattern to validate the issuer claim by adding an issuer_regex to the quirks map when creating the provider configuration:

{ok, {ProviderConfig, _}} =
    oidcc_provider_configuration:load_configuration(Issuer, #{
        quirks => #{
            issuer_regex => <<"^https://accounts\\.example\\.com/[a-z0-9]+">>
        }
    }),

This will allow tokens with issuer claims that match the regex pattern to validate successfully.