%%%------------------------------------------------------------------- %% @doc OpenID Connect High Level Interface %% %%

Setup

%% %% ``` %% {ok, Pid} = %% oidcc_provider_configuration_worker:start_link(#{ %% issuer => <<"https://accounts.google.com">>, %% name => {local, google_config_provider} %% }). %% ''' %% %% (or via a `supervisor') %% %% See {@link oidcc_provider_configuration_worker} for details %% %%

Global Configuration

%% %% %% @end %% @since 3.0.0 %%%------------------------------------------------------------------- -module(oidcc). -feature(maybe_expr, enable). -export([client_credentials_token/4]). -export([create_redirect_url/4]). -export([initiate_logout_url/4]). -export([introspect_token/5]). -export([jwt_profile_token/6]). -export([refresh_token/5]). -export([retrieve_token/5]). -export([retrieve_userinfo/5]). %% @doc %% Create Auth Redirect URL %% %%

Examples

%% %% ``` %% {ok, RedirectUri} = %% oidcc:create_redirect_url( %% provider_name, %% <<"client_id">>, %% <<"client_secret">> %% #{redirect_uri: <<"https://my.server/return"} %% ), %% %% %% RedirectUri = https://my.provider/auth?scope=openid&response_type=code&client_id=client_id&redirect_uri=https%3A%2F%2Fmy.server%2Freturn %% ''' %% @end %% @since 3.0.0 -spec create_redirect_url( ProviderConfigurationWorkerName, ClientId, ClientSecret, Opts ) -> {ok, Uri} | {error, oidcc_client_context:error() | oidcc_authorization:error()} when ProviderConfigurationWorkerName :: gen_server:server_ref(), ClientId :: binary(), ClientSecret :: binary() | unauthenticated, Opts :: oidcc_authorization:opts() | oidcc_client_context:opts(), Uri :: uri_string:uri_string(). create_redirect_url(ProviderConfigurationWorkerName, ClientId, ClientSecret, Opts) -> {ClientContextOpts, OtherOpts} = extract_client_context_opts(Opts), maybe {ok, ClientContext} ?= oidcc_client_context:from_configuration_worker( ProviderConfigurationWorkerName, ClientId, ClientSecret, ClientContextOpts ), oidcc_authorization:create_redirect_url(ClientContext, OtherOpts) end. %% @doc %% 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 %% %%

Examples

%% %% ``` %% %% Get AuthCode from Redirect %% %% {ok, #oidcc_token{}} = %% oidcc:retrieve_token( %% AuthCode, %% provider_name, %% <<"client_id">>, %% <<"client_secret">>, %% #{redirect_uri => <<"https://example.com/callback">>} %% ). %% ''' %% @end %% @since 3.0.0 -spec retrieve_token( AuthCode, ProviderConfigurationWorkerName, ClientId, ClientSecret | unauthenticated, Opts ) -> {ok, oidcc_token:t()} | {error, oidcc_client_context:error() | oidcc_token:error()} when AuthCode :: binary(), ProviderConfigurationWorkerName :: gen_server:server_ref(), ClientId :: binary(), ClientSecret :: binary(), Opts :: oidcc_token:retrieve_opts() | oidcc_client_context:opts(). retrieve_token( AuthCode, ProviderConfigurationWorkerName, ClientId, ClientSecret, Opts ) -> {ClientContextOpts, OtherOpts} = extract_client_context_opts(Opts), RefreshJwksFun = oidcc_jwt_util:refresh_jwks_fun(ProviderConfigurationWorkerName), OptsWithRefresh = maps_put_new(refresh_jwks, RefreshJwksFun, OtherOpts), maybe {ok, ClientContext} ?= oidcc_client_context:from_configuration_worker( ProviderConfigurationWorkerName, ClientId, ClientSecret, ClientContextOpts ), oidcc_token:retrieve(AuthCode, ClientContext, OptsWithRefresh) end. %% @doc %% Load userinfo for the given token %% %%

Examples

%% %% ``` %% %% Get Token %% %% {ok, #{<<"sub">> => Sub}} = %% oidcc:retrieve_userinfo( %% Token, %% provider_name, %% <<"client_id">>, %% <<"client_secret">>, %% #{} %% ). %% ''' %% @end %% @since 3.0.0 -spec retrieve_userinfo ( Token, ProviderConfigurationWorkerName, ClientId, ClientSecret | unauthenticated, Opts ) -> {ok, map()} | {error, oidcc_client_context:error() | oidcc_userinfo:error()} when Token :: oidcc_token:t(), ProviderConfigurationWorkerName :: gen_server:server_ref(), ClientId :: binary(), ClientSecret :: binary() | unauthenticated, Opts :: oidcc_userinfo:retrieve_opts_no_sub() | oidcc_client_context:opts(); (Token, ProviderConfigurationWorkerName, ClientId, ClientSecret, Opts) -> {ok, map()} | {error, any()} when Token :: binary(), ProviderConfigurationWorkerName :: gen_server:server_ref(), ClientId :: binary(), ClientSecret :: binary(), Opts :: oidcc_userinfo:retrieve_opts(). retrieve_userinfo( Token, ProviderConfigurationWorkerName, ClientId, ClientSecret, Opts ) -> {ClientContextOpts, OtherOpts} = extract_client_context_opts(Opts), maybe {ok, ClientContext} ?= oidcc_client_context:from_configuration_worker( ProviderConfigurationWorkerName, ClientId, ClientSecret, ClientContextOpts ), oidcc_userinfo:retrieve(Token, ClientContext, OtherOpts) end. %% @doc Refresh Token %% %%

Examples

%% %% ``` %% %% Get Token and wait for its expiry %% %% {ok, #oidcc_token{}} = %% oidcc:refresh_token( %% Token, %% provider_name, %% <<"client_id">>, %% <<"client_secret">>, %% #{expected_subject => <<"sub_from_initial_id_token>>} %% ). %% ''' %% @end %% @since 3.0.0 -spec refresh_token ( RefreshToken, ProviderConfigurationWorkerName, ClientId, ClientSecret | unauthenticated, Opts ) -> {ok, oidcc_token:t()} | {error, oidcc_client_context:error() | oidcc_token:error()} when RefreshToken :: binary(), ProviderConfigurationWorkerName :: gen_server:server_ref(), ClientId :: binary(), ClientSecret :: binary(), Opts :: oidcc_token:refresh_opts() | oidcc_client_context:opts(); ( Token, ProviderConfigurationWorkerName, ClientId, ClientSecret, Opts ) -> {ok, oidcc_token:t()} | {error, oidcc_client_context:error() | oidcc_token:error()} when Token :: oidcc_token:t(), ProviderConfigurationWorkerName :: gen_server:server_ref(), ClientId :: binary(), ClientSecret :: binary(), Opts :: oidcc_token:refresh_opts_no_sub(). refresh_token( RefreshToken, ProviderConfigurationWorkerName, ClientId, ClientSecret, Opts ) -> {ClientContextOpts, OtherOpts} = extract_client_context_opts(Opts), RefreshJwksFun = oidcc_jwt_util:refresh_jwks_fun(ProviderConfigurationWorkerName), OptsWithRefresh = maps_put_new(refresh_jwks, RefreshJwksFun, OtherOpts), maybe {ok, ClientContext} ?= oidcc_client_context:from_configuration_worker( ProviderConfigurationWorkerName, ClientId, ClientSecret, ClientContextOpts ), oidcc_token:refresh(RefreshToken, ClientContext, OptsWithRefresh) end. %% @doc %% Introspect the given access token %% %%

Examples

%% %% ``` %% %% Get AccessToken %% %% {ok, #oidcc_token_introspection{active = True}} = %% oidcc:introspect_token( %% AccessToken, %% provider_name, %% <<"client_id">>, %% <<"client_secret">>, %% #{} %% ). %% ''' %% @end %% @since 3.0.0 -spec introspect_token( Token, ProviderConfigurationWorkerName, ClientId, ClientSecret, Opts ) -> {ok, oidcc_token_introspection:t()} | {error, oidcc_client_context:error() | oidcc_token_introspection:error()} when Token :: oidcc_token:t() | binary(), ProviderConfigurationWorkerName :: gen_server:server_ref(), ClientId :: binary(), ClientSecret :: binary(), Opts :: oidcc_token_introspection:opts() | oidcc_client_context:opts(). introspect_token( Token, ProviderConfigurationWorkerName, ClientId, ClientSecret, Opts ) -> {ClientContextOpts, OtherOpts} = extract_client_context_opts(Opts), maybe {ok, ClientContext} ?= oidcc_client_context:from_configuration_worker( ProviderConfigurationWorkerName, ClientId, ClientSecret, ClientContextOpts ), oidcc_token_introspection:introspect(Token, ClientContext, OtherOpts) end. %% @doc Retrieve JSON Web Token (JWT) Profile Token %% %% See [https://datatracker.ietf.org/doc/html/rfc7523#section-4] %% %%

Examples

%% %% ``` %% {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">>, %% provider_name, %% <<"client_id">>, %% <<"client_secret">>, %% Key, %% #{ %% scope => [<<"scope">>], %% kid => maps:get(<<"keyId">>, KeyMap) %% } %% ). %% ''' %% @end %% @since 3.0.0 -spec jwt_profile_token( Subject, ProviderConfigurationWorkerName, ClientId, ClientSecret | unauthenticated, Jwk, Opts ) -> {ok, oidcc_token:t()} | {error, oidcc_client_context:error() | oidcc_token:error()} when Subject :: binary(), ProviderConfigurationWorkerName :: gen_server:server_ref(), ClientId :: binary(), ClientSecret :: binary(), Jwk :: jose_jwk:key(), Opts :: oidcc_token:jwt_profile_opts() | oidcc_client_context:opts(). jwt_profile_token(Subject, ProviderConfigurationWorkerName, ClientId, ClientSecret, Jwk, Opts) -> {ClientContextOpts, OtherOpts} = extract_client_context_opts(Opts), RefreshJwksFun = oidcc_jwt_util:refresh_jwks_fun(ProviderConfigurationWorkerName), OptsWithRefresh = maps_put_new(refresh_jwks, RefreshJwksFun, OtherOpts), maybe {ok, ClientContext} ?= oidcc_client_context:from_configuration_worker( ProviderConfigurationWorkerName, ClientId, ClientSecret, ClientContextOpts ), oidcc_token:jwt_profile(Subject, ClientContext, Jwk, OptsWithRefresh) end. %% @doc Retrieve Client Credential Token %% %% See [https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.4] %% %%

Examples

%% %% ``` %% {ok, #oidcc_token{}} = %% oidcc:client_credentials_token( %% provider_name, %% <<"client_id">>, %% <<"client_secret">>, %% #{scope => [<<"scope">>]} %% ). %% ''' %% @end %% @since 3.0.0 -spec client_credentials_token( ProviderConfigurationWorkerName, ClientId, ClientSecret, Opts ) -> {ok, oidcc_token:t()} | {error, oidcc_client_context:error() | oidcc_token:error()} when ProviderConfigurationWorkerName :: gen_server:server_ref(), ClientId :: binary(), ClientSecret :: binary(), Opts :: oidcc_token:client_credentials_opts() | oidcc_client_context:opts(). client_credentials_token(ProviderConfigurationWorkerName, ClientId, ClientSecret, Opts) -> {ClientContextOpts, OtherOpts} = extract_client_context_opts(Opts), RefreshJwksFun = oidcc_jwt_util:refresh_jwks_fun(ProviderConfigurationWorkerName), OptsWithRefresh = maps_put_new(refresh_jwks, RefreshJwksFun, OtherOpts), maybe {ok, ClientContext} ?= oidcc_client_context:from_configuration_worker( ProviderConfigurationWorkerName, ClientId, ClientSecret, ClientContextOpts ), oidcc_token:client_credentials(ClientContext, OptsWithRefresh) end. %% @doc %% Create Initiate URI for Relaying Party initated Logout %% %% See [https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout] %% %%

Examples

%% %% ``` %% %% Get `Token` from `oidcc_token` %% %% {ok, RedirectUri} = %% oidcc:initiate_logout_url( %% Token, %% provider_name, %% <<"client_id">>, %% #{post_logout_redirect_uri: <<"https://my.server/return"} %% ), %% %% %% RedirectUri = https://my.provider/logout?id_token_hint=IDToken&client_id=ClientId&post_logout_redirect_uri=https%3A%2F%2Fmy.server%2Freturn %% ''' %% @end %% @since 3.0.0 -spec initiate_logout_url( Token, ProviderConfigurationWorkerName, ClientId, Opts ) -> {ok, uri_string:uri_string()} | {error, oidcc_client_context:error() | oidcc_logout:error()} when Token :: IdToken | oidcc_token:t() | undefined, IdToken :: binary(), ProviderConfigurationWorkerName :: gen_server:server_ref(), ClientId :: binary(), Opts :: oidcc_logout:initiate_url_opts() | oidcc_client_context:unauthenticated_opts(). initiate_logout_url(Token, ProviderConfigurationWorkerName, ClientId, Opts) -> {ClientContextOpts, OtherOpts} = extract_client_context_opts(Opts), maybe {ok, ClientContext} ?= oidcc_client_context:from_configuration_worker( ProviderConfigurationWorkerName, ClientId, unauthenticated, ClientContextOpts ), oidcc_logout:initiate_url(Token, ClientContext, OtherOpts) end. -spec maps_put_new(Key, Value, Map1) -> Map2 when Key :: term(), Value :: term(), Map1 :: map(), Map2 :: map(). maps_put_new(Key, Value, Map) -> case maps:is_key(Key, Map) of true -> Map; false -> maps:put(Key, Value, Map) end. -spec extract_client_context_opts(Opts) -> {ClientContextOpts, RestOpts} when Opts :: RestOpts | ClientContextOpts, RestOpts :: map(), ClientContextOpts :: oidcc_client_context:opts(). extract_client_context_opts(Opts) -> { maps:with([client_jwks], Opts), maps:without([client_jwks], Opts) }.