Packages
oidcc
3.7.0
3.7.2
3.7.1
3.7.0
3.6.0
3.5.2
3.5.1
3.5.0
retired
3.4.0
3.3.0
3.2.6
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.2.0-beta.3
3.2.0-beta.2
retired
3.2.0-beta.1
retired
3.1.2
3.1.2-beta.1
retired
3.1.1
retired
3.1.0
retired
3.1.0-beta.2
retired
3.1.0-beta.1
retired
3.0.2
3.0.1
retired
3.0.0
retired
3.0.0-rc.6
retired
3.0.0-rc.5
retired
3.0.0-rc.4
retired
3.0.0-rc.3
retired
3.0.0-rc.2
retired
3.0.0-rc.1
retired
3.0.0-alpha.5
retired
3.0.0-alpha.4
retired
3.0.0-alpha.3
retired
3.0.0-alpha.2
retired
3.0.0-alpha.1
retired
2.0.0-alpha.2
retired
2.0.0-alpha.1
retired
1.8.1
retired
1.8.0
retired
1.7.0
retired
1.6.0
retired
1.5.1
retired
1.5.0
retired
1.4.0
retired
1.3.0
retired
1.2.1
retired
1.2.0
retired
1.1.0
retired
1.0.1
retired
1.0.0
retired
OpenID Connect client library for the BEAM.
Current section
Files
Jump to
Current section
Files
src/oidcc_client_registration.erl
-module(oidcc_client_registration).
-feature(maybe_expr, enable).
-include("internal/doc.hrl").
?MODULEDOC("""
Dynamic Client Registration Utilities.
See https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata.
## Records
To use the record, import the definition:
```erlang
-include_lib(["oidcc/include/oidcc_client_registration.hrl"]).
```
## Telemetry
See [`Oidcc.ClientRegistration`](`m:'Elixir.Oidcc.ClientRegistration'`).
""").
?MODULEDOC(#{since => <<"3.0.0">>}).
-include("oidcc_client_registration.hrl").
-include("oidcc_provider_configuration.hrl").
-export([register/3]).
-export_type([error/0]).
-export_type([opts/0]).
-export_type([response/0]).
-export_type([t/0]).
?DOC("""
Configure configuration loading / parsing.
## Parameters
* `initial_access_token` - Access Token for registration
* `request_opts` - config for HTTP request
""").
?DOC(#{since => <<"3.0.0">>}).
-type opts() :: #{
initial_access_token => binary() | undefined,
request_opts => oidcc_http_util:request_opts()
}.
?DOC("""
Record containing Client Registration Metadata.
See https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata and
https://openid.net/specs/openid-connect-rpinitiated-1_0.html#ClientMetadata.
All unrecognized fields are stored in `extra_fields`.
""").
?DOC(#{since => <<"3.0.0">>}).
-type t() ::
#oidcc_client_registration{
%% OpenID Connect Dynamic Client Registration 1.0
redirect_uris :: [uri_string:uri_string()],
%% OpenID Connect Dynamic Client Registration 1.0
response_types :: [binary()] | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
grant_types :: [binary()] | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
application_type :: web | native,
%% OpenID Connect Dynamic Client Registration 1.0
contacts :: [binary()] | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
client_name :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
logo_uri :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
client_uri :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
policy_uri :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
tos_uri :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
jwks :: jose_jwk:key() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
jwks_uri :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
sector_identifier_uri :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
subject_type :: pairwise | public | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
id_token_signed_response_alg :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
id_token_encrypted_response_alg :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
id_token_encrypted_response_enc :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
userinfo_signed_response_alg :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
userinfo_encrypted_response_alg :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
userinfo_encrypted_response_enc :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
request_object_signing_alg :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
request_object_encryption_alg :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
request_object_encryption_enc :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
token_endpoint_auth_method :: erlang:binary(),
%% OpenID Connect Dynamic Client Registration 1.0
token_endpoint_auth_signing_alg :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
default_max_age :: pos_integer() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
require_auth_time :: boolean(),
%% OpenID Connect Dynamic Client Registration 1.0
default_acr_values :: [binary()] | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
initiate_login_uri :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
request_uris :: [uri_string:uri_string()] | undefined,
%% OpenID Connect RP-Initiated Logout 1.0
post_logout_redirect_uris :: [uri_string:uri_string()] | undefined,
%% OAuth 2.0 Pushed Authorization Requests
require_pushed_authorization_requests :: boolean(),
%% OAuth 2.0 Demonstrating Proof of Possession (DPoP)
dpop_bound_access_tokens :: boolean(),
%% Unknown Fields
extra_fields :: #{binary() => term()}
}.
?DOC("""
Record containing Client Registration Response.
See https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse.
All unrecognized fields are stored in `extra_fields`.
""").
?DOC(#{since => <<"3.0.0">>}).
-type response() ::
#oidcc_client_registration_response{
client_id :: erlang:binary(),
client_secret :: binary() | undefined,
registration_access_token :: binary() | undefined,
registration_client_uri :: uri_string:uri_string() | undefined,
client_id_issued_at :: pos_integer() | undefined,
client_secret_expires_at :: pos_integer() | undefined,
%% Unknown Fields
extra_fields :: #{binary() => term()}
}.
?DOC(#{since => <<"3.0.0">>}).
-type error() ::
registration_not_supported
| invalid_content_type
| oidcc_decode_util:error()
| oidcc_http_util:error().
-telemetry_event(#{
event => [oidcc, register_client, start],
description => <<"Emitted at the start of registering the client">>,
measurements => <<"#{system_time => non_neg_integer()}">>,
metadata => <<"#{issuer => uri_string:uri_string()}">>
}).
-telemetry_event(#{
event => [oidcc, register_client, stop],
description => <<"Emitted at the end of registering the client">>,
measurements => <<"#{duration => integer(), monotonic_time => integer()}">>,
metadata => <<"#{issuer => uri_string:uri_string()}">>
}).
-telemetry_event(#{
event => [oidcc, register_client, exception],
description => <<"Emitted at the end of registering the client">>,
measurements => <<"#{duration => integer(), monotonic_time => integer()}">>,
metadata => <<"#{issuer => uri_string:uri_string()}">>
}).
?DOC("""
Register Client.
## Examples
```erlang
{ok, ProviderConfiguration} =
oidcc_provider_configuration:load_configuration("https://your.issuer"),
{ok, #oidcc_client_registration_response{
client_id = ClientId,
client_secret = ClientSecret
}} =
oidcc_client_registration:register(
ProviderConfiguration,
#oidcc_client_registration{
redirect_uris = ["https://your.application.com/oidcc/callback"]
},
#{initial_access_token => <<"optional token you got from the provider">>}
).
```
""").
?DOC(#{since => <<"3.0.0">>}).
-spec register(ProviderConfiguration, Registration, Opts) ->
{ok, response()} | {error, error()}
when
ProviderConfiguration :: oidcc_provider_configuration:t(),
Registration :: t(),
Opts :: opts().
register(#oidcc_provider_configuration{registration_endpoint = undefined}, _Registration, _Opts) ->
{error, registration_not_supported};
register(
#oidcc_provider_configuration{issuer = Issuer, registration_endpoint = RegistrationEndpoint},
Registration,
Opts
) ->
RegistrationBody = encode(Registration),
TelemetryOpts = #{topic => [oidcc, register_client], extra_meta => #{issuer => Issuer}},
RequestOpts = maps:get(request_opts, Opts, #{}),
Headers =
case maps:get(initial_access_token, Opts, undefined) of
undefined -> [];
Token -> [{"authorization", ["Bearer ", Token]}]
end,
Request = {RegistrationEndpoint, Headers, "application/json", RegistrationBody},
maybe
{ok, {{json, ResponseMap}, _Headers}} ?=
oidcc_http_util:request(post, Request, TelemetryOpts, RequestOpts),
{ok, #oidcc_client_registration_response{} = Response} ?= decode_response(ResponseMap),
{ok, Response}
else
{error, Reason} -> {error, Reason};
{ok, {{_Format, _Body}, _}} -> {error, invalid_content_type}
end.
-spec decode_response(Response) -> {ok, response()} | {error, error()} when Response :: map().
decode_response(Response) ->
case
oidcc_decode_util:extract(
Response,
[
{required, client_id, fun oidcc_decode_util:parse_setting_binary/2},
{optional, client_secret, undefined, fun oidcc_decode_util:parse_setting_binary/2},
{optional, registration_access_token, undefined,
fun oidcc_decode_util:parse_setting_binary/2},
{optional, registration_client_uri, undefined,
fun oidcc_decode_util:parse_setting_uri_https/2},
{optional, client_id_issued_at, undefined,
fun oidcc_decode_util:parse_setting_number/2},
{optional, client_secret_expires_at, undefined,
fun oidcc_decode_util:parse_setting_number/2}
],
#{}
)
of
{ok, {
#{
client_id := ClientId,
client_secret := ClientSecret,
registration_access_token := RegistrationAccessToken,
registration_client_uri := RegistrationClientUri,
client_id_issued_at := ClientIdIssuedAt,
client_secret_expires_at := ClientSecretExpiresAt
},
ExtraFields
}} ->
{ok, #oidcc_client_registration_response{
client_id = ClientId,
client_secret = ClientSecret,
registration_access_token = RegistrationAccessToken,
registration_client_uri = RegistrationClientUri,
client_id_issued_at = ClientIdIssuedAt,
client_secret_expires_at = ClientSecretExpiresAt,
extra_fields = ExtraFields
}};
{error, Reason} ->
{error, Reason}
end.
-spec encode(Metadata) -> binary() when Metadata :: t().
encode(#oidcc_client_registration{
redirect_uris = RedirectUris,
response_types = ResponseTypes,
grant_types = GrantTypes,
application_type = ApplicationType,
contacts = Contacts,
client_name = ClientName,
logo_uri = LogoUri,
client_uri = ClientUri,
policy_uri = PolicyUri,
tos_uri = TosUri,
jwks = Jwks,
sector_identifier_uri = SectorIdentifierUri,
subject_type = SubjectType,
id_token_signed_response_alg = IdTokenSignedResponseAlg,
id_token_encrypted_response_alg = IdTokenencryptedResponseAlg,
id_token_encrypted_response_enc = IdTokenEncryptedResponseEnc,
userinfo_signed_response_alg = UserinfoSignedResponseAlg,
userinfo_encrypted_response_alg = UserinfoEncryptedResponseAlg,
userinfo_encrypted_response_enc = UserinfoEncryptedResponseEnc,
request_object_signing_alg = RequestObjectSigningAlg,
request_object_encryption_alg = RequestObjectEncryptionAlg,
request_object_encryption_enc = RequestObjectEncryptionEnc,
token_endpoint_auth_method = TokenEndpointAuthMethod,
token_endpoint_auth_signing_alg = TokenEndpointAuthSigningAlg,
default_max_age = DefaultMaxAge,
require_auth_time = RequireAuthTime,
default_acr_values = DefaultAcrValues,
initiate_login_uri = InitiateLoginUri,
request_uris = RequestUris,
post_logout_redirect_uris = PostLogoutRedirectUris,
require_pushed_authorization_requests = RequirePushedAuthorizationRequests,
extra_fields = ExtraFields
}) ->
Map0 = #{
redirect_uris => RedirectUris,
response_types => ResponseTypes,
grant_types => GrantTypes,
application_type => ApplicationType,
contacts => Contacts,
client_name => ClientName,
logo_uri => LogoUri,
client_uri => ClientUri,
policy_uri => PolicyUri,
tos_uri => TosUri,
jwks =>
case Jwks of
undefined ->
undefined;
_ ->
{_KeyType, KeyMap} = jose_jwk:to_map(Jwks),
KeyMap
end,
sector_identifier_uri => SectorIdentifierUri,
subject_type => SubjectType,
id_token_signed_response_alg => IdTokenSignedResponseAlg,
id_token_encrypted_response_alg => IdTokenencryptedResponseAlg,
id_token_encrypted_response_enc => IdTokenEncryptedResponseEnc,
userinfo_signed_response_alg => UserinfoSignedResponseAlg,
userinfo_encrypted_response_alg => UserinfoEncryptedResponseAlg,
userinfo_encrypted_response_enc => UserinfoEncryptedResponseEnc,
request_object_signing_alg => RequestObjectSigningAlg,
request_object_encryption_alg => RequestObjectEncryptionAlg,
request_object_encryption_enc => RequestObjectEncryptionEnc,
token_endpoint_auth_method => TokenEndpointAuthMethod,
token_endpoint_auth_signing_alg => TokenEndpointAuthSigningAlg,
default_max_age => DefaultMaxAge,
require_auth_time => RequireAuthTime,
default_acr_values => DefaultAcrValues,
initiate_login_uri => InitiateLoginUri,
request_uris => RequestUris,
post_logout_redirect_uris => PostLogoutRedirectUris,
require_pushed_authorization_requests => RequirePushedAuthorizationRequests
},
Map1 = maps:merge(Map0, ExtraFields),
Map = maps:filter(
fun
(_Key, undefined) -> false;
(_Key, _Value) -> true
end,
Map1
),
jose:encode(Map).