Facilitate OpenID Code/Token Exchanges
Telemetry
[:oidcc, :request_token, :start]- Description: Emitted at the start of requesting a code token
- Measurements:
%{system_time: non_neg_integer(), monotonic_time: integer()} - Metadata:
%{issuer: :uri_string.uri_string(), client_id: String.t()}
[:oidcc, :request_token, :stop]- Description: Emitted at the end of requesting a code token
- Measurements:
%{duration: integer(), monotonic_time: integer()} - Metadata:
%{issuer: :uri_string.uri_string(), client_id: String.t()}
[:oidcc, :request_token, :exception]- Description: Emitted at the end of requesting a code token
- Measurements:
%{duration: integer(), monotonic_time: integer()} - Metadata:
%{issuer: :uri_string.uri_string(), client_id: String.t()}
[:oidcc, :refresh_token, :start]- Description: Emitted at the start of refreshing a token
- Measurements:
%{system_time: non_neg_integer(), monotonic_time: integer()} - Metadata:
%{issuer: :uri_string.uri_string(), client_id: String.t()}
[:oidcc, :refresh_token, :stop]- Description: Emitted at the end of refreshing a token
- Measurements:
%{duration: integer(), monotonic_time: integer()} - Metadata:
%{issuer: :uri_string.uri_string(), client_id: String.t()}
[:oidcc, :refresh_token, :exception]- Description: Emitted at the end of refreshing a token
- Measurements:
%{duration: integer(), monotonic_time: integer()} - Metadata:
%{issuer: :uri_string.uri_string(), client_id: String.t()}
[:oidcc, :jwt_profile_token, :start]- Description: Emitted at the start of exchanging a JWT profile token
- Measurements:
%{system_time: non_neg_integer(), monotonic_time: integer()} - Metadata:
%{issuer: :uri_string.uri_string(), client_id: String.t()}
[:oidcc, :jwt_profile_token, :stop]- Description: Emitted at the end of exchanging a JWT profile token
- Measurements:
%{duration: integer(), monotonic_time: integer()} - Metadata:
%{issuer: :uri_string.uri_string(), client_id: String.t()}
[:oidcc, :jwt_profile_token, :exception]- Description: Emitted at the end of exchanging a JWT profile token
- Measurements:
%{duration: integer(), monotonic_time: integer()} - Metadata:
%{issuer: :uri_string.uri_string(), client_id: String.t()}
[:oidcc, :client_credentials, :start]- Description: Emitted at the start of requesting a client credentials token
- Measurements:
%{system_time: non_neg_integer(), monotonic_time: integer()} - Metadata:
%{issuer: :uri_string.uri_string(), client_id: String.t()}
[:oidcc, :client_credentials, :stop]- Description: Emitted at the end of requesting a client credentials token
- Measurements:
%{duration: integer(), monotonic_time: integer()} - Metadata:
%{issuer: :uri_string.uri_string(), client_id: String.t()}
[:oidcc, :client_credentials, :exception]- Description: Emitted at the end of requesting a client credentials token
- Measurements:
%{duration: integer(), monotonic_time: integer()} - Metadata:
%{issuer: :uri_string.uri_string(), client_id: String.t()}
Summary
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.
Retrieve the token, reporting what a JWKS refresh fetched
Validate ID Token
Validate the JARM response, returning the valid claims as a map.
Validate JWT
Types
@type retrieve_opts() :: :oidcc_token.retrieve_opts()
@type t() :: %Oidcc.Token{ access: Oidcc.Token.Access.t() | none(), id: Oidcc.Token.Id.t() | none(), refresh: Oidcc.Token.Refresh.t() | none(), scope: :oidcc_scope.scopes() }
Functions
@spec client_credentials( client_context :: Oidcc.ClientContext.t(), opts :: :oidcc_token.client_credentials_opts() ) :: {:ok, t()} | {:error, :oidcc_token.error()}
Retrieve Client Credential Token
See https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.4
For a high level interface using Oidcc.ProviderConfiguration.Worker
see Oidcc.client_credentials_token/4.
Examples
iex> {:ok, pid} =
...> Oidcc.ProviderConfiguration.Worker.start_link(%{
...> issuer: "https://erlef-test-w4a8z2.zitadel.cloud"
...> })
...>
...> {:ok, client_context} =
...> Oidcc.ClientContext.from_configuration_worker(
...> pid,
...> System.fetch_env!("CLIENT_CREDENTIALS_CLIENT_ID"),
...> System.fetch_env!("CLIENT_CREDENTIALS_CLIENT_SECRET")
...> )
...>
...> {:ok, %Oidcc.Token{}} =
...> Oidcc.Token.client_credentials(
...> client_context,
...> %{scope: ["openid"]}
...> )
@spec jwt_profile( subject :: String.t(), client_context :: Oidcc.ClientContext.t(), jwk :: JOSE.JWK.t(), opts :: :oidcc_token.jwt_profile_opts() ) :: {:ok, t()} | {:error, :oidcc_token.error()}
Retrieve JSON Web Token (JWT) Profile Token
See https://datatracker.ietf.org/doc/html/rfc7523#section-4
For a high level interface using Oidcc.ProviderConfiguration.Worker
see Oidcc.jwt_profile_token/6.
Examples
iex> {:ok, pid} =
...> Oidcc.ProviderConfiguration.Worker.start_link(%{
...> issuer: "https://erlef-test-w4a8z2.zitadel.cloud"
...> })
...>
...> {:ok, client_context} =
...> Oidcc.ClientContext.from_configuration_worker(
...> pid,
...> "JWT Profile Test",
...> "client_secret"
...> )
...>
...> %{"key" => key, "keyId" => kid, "userId" => subject} = "JWT_PROFILE"
...> |> System.fetch_env!()
...> |> JOSE.decode()
...>
...> jwk = JOSE.JWK.from_pem(key)
...>
...> {:ok, %Oidcc.Token{}} =
...> Oidcc.Token.jwt_profile(
...> subject,
...> client_context,
...> jwk,
...> %{scope: ["openid", "urn:zitadel:iam:org:project:id:zitadel:aud"], kid: kid}
...> )
@spec refresh( refresh_token :: String.t(), client_context :: Oidcc.ClientContext.t(), opts :: :oidcc_token.refresh_opts() ) :: {:ok, t()} | {:error, :oidcc_token.error()}
@spec refresh( token :: t(), client_context :: Oidcc.ClientContext.t(), opts :: :oidcc_token.refresh_opts_no_sub() ) :: {:ok, t()} | {:error, :oidcc_token.error()}
Refresh Token
For a high level interface using Oidcc.ProviderConfiguration.Worker
see Oidcc.refresh_token/5.
Examples
iex> {:ok, pid} =
...> Oidcc.ProviderConfiguration.Worker.start_link(%{
...> issuer: "https://api.login.yahoo.com"
...> })
...>
...> {:ok, client_context} =
...> Oidcc.ClientContext.from_configuration_worker(
...> pid,
...> "client_id",
...> "client_secret"
...> )
...>
...> # Get refresh_token from redirect
...> refresh_token = "refresh_token"
...>
...> Oidcc.Token.refresh(
...> refresh_token,
...> client_context,
...> %{expected_subject: "sub"}
...> )
...> # => {:ok, %Oidcc.Token{}}
@spec retrieve( auth_code :: String.t(), client_context :: Oidcc.ClientContext.t(), opts :: retrieve_opts() ) :: {:ok, t()} | {:error, :oidcc_token.error()}
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.ProviderConfiguration.Worker
see Oidcc.retrieve_token/5.
Examples
iex> {:ok, pid} =
...> Oidcc.ProviderConfiguration.Worker.start_link(%{
...> issuer: "https://api.login.yahoo.com"
...> })
...>
...> {:ok, client_context} =
...> Oidcc.ClientContext.from_configuration_worker(
...> pid,
...> "client_id",
...> "client_secret"
...> )
...>
...> # Get auth_code from redirect
...> auth_code = "auth_code"
...>
...> Oidcc.Token.retrieve(
...> auth_code,
...> client_context,
...> %{redirect_uri: "https://my.server/return"}
...> )
...> # => {:ok, %Oidcc.Token{}}
@spec retrieve_with_refresh( auth_code :: String.t(), client_context :: Oidcc.ClientContext.t(), opts :: retrieve_opts() ) :: {:ok, t(), :oidcc_token.refresh_info()} | {:error, :oidcc_token.error()}
Retrieve the token, reporting what a JWKS refresh fetched
Same as retrieve/3, but the third element carries whatever the
refresh_jwks function returned alongside the refreshed keys, or :undefined
when no refresh happened.
oidcc refreshes the keys and retries validation without re-sending the authorization code, which is single use, so this is the only way to learn what that refresh fetched. Persisting the refreshed key set is the usual reason to want it.
Examples
refresh_jwks = fn _old_jwks, _kid ->
{:ok, {jwks, expiry, document}} =
Oidcc.ProviderConfiguration.load_jwks_raw(jwks_uri, %{})
{:ok, JOSE.JWK.to_record(jwks), {document, expiry}}
end
{:ok, %Oidcc.Token{}, {document, expiry}} =
Oidcc.Token.retrieve_with_refresh(auth_code, client_context, %{
redirect_uri: "https://my.server/return",
refresh_jwks: refresh_jwks
})
@spec validate_id_token( id_token :: String.t(), client_context :: Oidcc.ClientContext.t(), nonce_or_opts :: String.t() | :any | retrieve_opts() ) :: {:ok, :oidcc_jwt_util.claims()} | {:error, :oidcc_token.error()}
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.
Examples
iex> {:ok, pid} =
...> Oidcc.ProviderConfiguration.Worker.start_link(%{
...> issuer: "https://api.login.yahoo.com"
...> })
...>
...> {:ok, client_context} =
...> Oidcc.ClientContext.from_configuration_worker(
...> pid,
...> "client_id",
...> "client_secret"
...> )
...>
...> #Get IdToken from somewhere
...> id_token = "id_token"
...>
...> Oidcc.Token.validate_id_token(id_token, client_context, :any)
...> # => {:ok, %{"sub" => "sub", ... }}
@spec validate_jarm( response :: String.t(), client_context :: Oidcc.ClientContext.t(), opts :: :oidcc_token.validate_jarm_opts() ) :: {:ok, :oidcc_jwt_util.claims()} | {:error, :oidcc_token.error()}
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
iex> {:ok, pid} =
...> Oidcc.ProviderConfiguration.Worker.start_link(%{
...> issuer: "https://api.login.yahoo.com"
...> })
...>
...> {:ok, client_context} =
...> Oidcc.ClientContext.from_configuration_worker(
...> pid,
...> "client_id",
...> "client_secret"
...> )
...>
...> # Get auth_code from redirect
...> response = "JWT"
...>
...> Oidcc.Token.validate_jarm(
...> response,
...> client_context,
...> %{}
...> )
...> # => {:ok, %{"code" => auth_code}}
@spec validate_jwt( jwt :: String.t(), client_context :: Oidcc.ClientContext.t(), opts :: :oidcc_token.validate_jwt_opts() ) :: {:ok, :oidcc_jwt_util.claims()} | {:error, :oidcc_token.error()}
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.
Examples
iex> {:ok, pid} =
...> Oidcc.ProviderConfiguration.Worker.start_link(%{
...> issuer: "https://api.login.yahoo.com"
...> })
...>
...> {:ok, client_context} =
...> Oidcc.ClientContext.from_configuration_worker(
...> pid,
...> "client_id",
...> "client_secret"
...> )
...>
...> # Get JWT from Authorization header
...> jwt = "jwt"
...>
...> opts = %{
...> signing_algs: client_context.provider_configuration.id_token_signing_alg_values_supported
...> }
...>
...> Oidcc.Token.validate_jwt(jwt, client_context, opts)
...> # => {:ok, %{"sub" => "sub", ... }}