Skip to content

Commit

Permalink
Implement RP Initiated Logout (#258)
Browse files Browse the repository at this point in the history
  • Loading branch information
maennchen authored Sep 26, 2023
1 parent a7cb4f3 commit b9cc5ab
Show file tree
Hide file tree
Showing 15 changed files with 503 additions and 9 deletions.
33 changes: 33 additions & 0 deletions include/oidcc_client_registration.hrl
Original file line number Diff line number Diff line change
@@ -1,37 +1,70 @@
-ifndef(OIDCC_CLIENT_REGISTRATION_HRL).

%% @see https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata
%% @see https://openid.net/specs/openid-connect-rpinitiated-1_0.html#ClientMetadata
-record(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 = undefined :: [binary()] | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
grant_types = undefined :: [binary()] | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
application_type = web :: web | native,
%% OpenID Connect Dynamic Client Registration 1.0
contacts = undefined :: [binary()] | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
client_name = undefined :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
logo_uri = undefined :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
client_uri = undefined :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
policy_uri = undefined :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
tos_uri = undefined :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
jwks = undefined :: jose_jwk:key() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
jwks_uri = undefined :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
sector_identifier_uri = undefined :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
subject_type = undefined :: pairwise | public | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
id_token_signed_response_alg = undefined :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
id_token_encrypted_response_alg = undefined :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
id_token_encrypted_response_enc = undefined :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
userinfo_signed_response_alg = undefined :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
userinfo_encrypted_response_alg = undefined :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
userinfo_encrypted_response_enc = undefined :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
request_object_signing_alg = undefined :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
request_object_encryption_alg = undefined :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
request_object_encryption_enc = undefined :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
token_endpoint_auth_method = <<"client_secret_basic">> :: binary(),
%% OpenID Connect Dynamic Client Registration 1.0
token_endpoint_auth_signing_alg = undefined :: binary() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
default_max_age = undefined :: pos_integer() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
require_auth_time = false :: boolean(),
%% OpenID Connect Dynamic Client Registration 1.0
default_acr_values = undefined :: [binary()] | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
initiate_login_uri = undefined :: uri_string:uri_string() | undefined,
%% OpenID Connect Dynamic Client Registration 1.0
request_uris = undefined :: [uri_string:uri_string()] | undefined,
%% OpenID Connect RP-Initiated Logout 1.0
post_logout_redirect_uris = undefined :: [uri_string:uri_string()] | undefined,
%% Unknown Fields
extra_fields = #{} :: #{binary() => term()}
}).
Expand Down
3 changes: 3 additions & 0 deletions include/oidcc_provider_configuration.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

%% @see https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
%% @see https://datatracker.ietf.org/doc/html/draft-jones-oauth-discovery-01#section-4.1
%% @see https://openid.net/specs/openid-connect-rpinitiated-1_0.html#OPMetadata
-record(oidcc_provider_configuration,
%% OpenID Connect Discovery 1.0 / OAuth 2.0 Discovery (draft-jones-oauth-discovery-01)
{
Expand Down Expand Up @@ -90,6 +91,8 @@
[binary()] | undefined,
%% OAuth 2.0 Discovery (draft-jones-oauth-discovery-01)
code_challenge_methods_supported = undefined :: [binary()] | undefined,
%% OpenID Connect RP-Initiated Logout 1.0
end_session_endpoint = undefined :: uri_string:uri_string() | undefined,
%% Unknown Fields
extra_fields = #{} :: #{binary() => term()}
}
Expand Down
57 changes: 57 additions & 0 deletions lib/oidcc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,61 @@ defmodule Oidcc do
opts
)
|> Oidcc.Token.normalize_token_response()

@doc """
Create Initiate URI for Relaying Party initated Logout
See [https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout]
## Examples
iex> {:ok, pid} =
...> Oidcc.ProviderConfiguration.Worker.start_link(%{
...> issuer: "https://erlef-test-w4a8z2.zitadel.cloud"
...> })
...>
...> # Get access_token from Oidcc.Token.retrieve/3
...> token = "token"
...>
...> {:ok, _redirect_uri} = Oidcc.initiate_logout_url(
...> token,
...> pid,
...> "client_id",
...> "client_secret"
...> )
"""
@doc since: "3.0.0"
@spec initiate_logout_url(
token :: id_token | Oidcc.Token.t() | :undefined,
provider_configuration_name :: GenServer.name(),
client_id :: String.t(),
client_secret :: String.t(),
opts :: :oidcc_logout.initiate_url_opts() | :oidcc_client_context.opts()
) ::
{:ok, :uri_string.uri_string()}
| {:error, :oidcc_client_context.error() | :oidcc_logout.error()}
when id_token: String.t()
def initiate_logout_url(
token,
provider_configuration_name,
client_id,
client_secret,
opts \\ %{}
) do
token =
case token do
%Oidcc.Token{} = token -> Oidcc.Token.struct_to_record(token)
token when is_binary(token) -> token
:undefined -> :undefined
end

:oidcc.initiate_logout_url(
token,
provider_configuration_name,
client_id,
client_secret,
opts
)
end
end
4 changes: 3 additions & 1 deletion lib/oidcc/client_registration.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ defmodule Oidcc.ClientRegistration do
@typedoc """
Client Metdata Struct
See https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata
See https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata and
https://openid.net/specs/openid-connect-rpinitiated-1_0.html#ClientMetadata
"""
@typedoc since: "3.0.0"
@type t() :: %__MODULE__{
Expand Down Expand Up @@ -78,6 +79,7 @@ defmodule Oidcc.ClientRegistration do
default_acr_values: [String.t()] | :undefined,
initiate_login_uri: :uri_string.uri_string() | :undefined,
request_uris: [:uri_string.uri_string()] | :undefined,
post_logout_redirect_uris: [:uri_string.uri_string()] | :undefined,
extra_fields: %{String.t() => term()}
}

Expand Down
55 changes: 55 additions & 0 deletions lib/oidcc/logout.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
defmodule Oidcc.Logout do
@moduledoc """
Logout from the OpenID Provider
"""
@moduledoc since: "3.0.0"

alias Oidcc.ClientContext

@doc """
Initiate URI for Relaying Party initated Logout
See https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout
For a high level interface using `Oidcc.ProviderConfiguration.Worker`
see `Oidcc.initiate_logout_url/5`.
## 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,
...> "client_id",
...> "client_secret"
...> )
...>
...> # Get `token` from `Oidcc.retrieve_token/5`
...> token = "token"
...>
...> {:ok, _redirect_uri} =
...> Oidcc.Logout.initiate_url(
...> token,
...> client_context,
...> %{post_logout_redirect_uri: "https://my.server/return"}
...> )
"""
@doc since: "3.0.0"
@spec initiate_url(
token :: id_token | Oidcc.Token.t() | :undefined,
client_context :: ClientContext.t(),
opts :: :oidcc_logout.initiate_url_opts()
) ::
{:ok, :uri_string.uri_string()}
| {:error, :oidcc_logout.error()}
when id_token: String.t()
def initiate_url(token, client_context, opts \\ %{}) do
client_context = ClientContext.struct_to_record(client_context)

:oidcc_logout.initiate_url(token, client_context, opts)
end
end
2 changes: 2 additions & 0 deletions lib/oidcc/provider_configuration.ex
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ defmodule Oidcc.ProviderConfiguration do
For details on the fields see:
* https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
* https://datatracker.ietf.org/doc/html/draft-jones-oauth-discovery-01#section-4.1
* https://openid.net/specs/openid-connect-rpinitiated-1_0.html#OPMetadata
"""
@typedoc since: "3.0.0"
@type t() :: %__MODULE__{
Expand Down Expand Up @@ -108,6 +109,7 @@ defmodule Oidcc.ProviderConfiguration do
introspection_endpoint_auth_methods_supported: [String.t()],
introspection_endpoint_auth_signing_alg_values_supported: [String.t()] | :undefined,
code_challenge_methods_supported: [String.t()] | :undefined,
end_session_endpoint: :uri_string.uri_string() | :undefined,
extra_fields: %{String.t() => term()}
}

Expand Down
53 changes: 53 additions & 0 deletions src/oidcc.erl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

-export([client_credentials_token/4]).
-export([create_redirect_url/4]).
-export([initiate_logout_url/5]).
-export([introspect_token/5]).
-export([jwt_profile_token/6]).
-export([refresh_token/5]).
Expand Down Expand Up @@ -427,6 +428,58 @@ client_credentials_token(ProviderConfigurationWorkerName, ClientId, ClientSecret
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]
%%
%% <h2>Examples</h2>
%%
%% ```
%% %% Get `Token` from `oidcc_token`
%%
%% {ok, RedirectUri} =
%% oidcc:initiate_logout_url(
%% Token,
%% provider_name,
%% <<"client_id">>,
%% <<"client_secret">>,
%% #{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,
ClientSecret,
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(),
ClientSecret :: binary(),
Opts :: oidcc_logout:initiate_url_opts() | oidcc_client_context:opts().
initiate_logout_url(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_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) ->
Expand Down
Loading

0 comments on commit b9cc5ab

Please sign in to comment.