oidcc_token (Oidcc v3.5.1)
View SourceFacilitate 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.
Token Response Wrapper.
Options for refreshing a token.
Functions
Authorization headers
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 ID Token
Validate the JARM response, returning the valid claims as a map.
Validate JWT
Types
-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.
-type authorization_headers_opts() :: #{dpop_nonce => binary()}.
-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()}.
-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().
-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.
-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()}.
-type refresh() :: #oidcc_token_refresh{token :: binary()}.
Refresh Token Wrapper.
Fields
token
- The retrieved token.
-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()}.
-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()}.
-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
pkce_verifier
- PKCE verifier (random string previously given tooidcc_authorization
), see https://datatracker.ietf.org/doc/html/rfc7636#section-4.1.require_pkce
- whether to require PKCE when getting the token.nonce
- Nonce to check.scope
- Scope to store with the token.refresh_jwks
- How to handle tokens with an unknownkid
. Seeoidcc_jwt_util:refresh_jwks_for_unknown_kid_fun/0
.redirect_uri
- Redirect URI given tooidcc_authorization:create_redirect_url/2
.dpop_nonce
- if using DPoP, thenonce
value to use in the proof claim.trusted_audiences
- if present, a list of additional audience values to accept. Defaults toany
which allows any additional values.token_request_claims
- Additional claims to use with the token request.
-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
id
-id/0
.access
-access/0
.refresh
-refresh/0
.scope
-oidcc_scope:scopes/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
scope
- Scope to store with the token.refresh_jwks
- How to handle tokens with an unknownkid
. Seeoidcc_jwt_util:refresh_jwks_for_unknown_kid_fun/0
.expected_subject
-sub
of the original token.
-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
-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).
-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()}.
-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">>]}).
-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)}).
-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">>}).
-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">>}).
-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 ifissuer_regex
is configured in quirks.nonce
claim must match thenonce
option.aud
claim must match thetrusted_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.
-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/">>}).
-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 ifissuer_regex
is configured in quirks.aud
claim must match thetrusted_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.