Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FAPI2 profile support #317

Merged
merged 11 commits into from
Dec 27, 2023
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ The refactoring for `v3` and the certification is funded as an
* Logout
* [RP-Initiated](https://openid.net/specs/openid-connect-rpinitiated-1_0.html)
* [Demonstrating Proof of Possession (DPoP)](https://www.rfc-editor.org/rfc/rfc9449)
* Profiles
* [FAPI 2.0 Security Profile](https://openid.bitbucket.io/fapi/fapi-2_0-security-profile.html)
* [FAPI 2.0 Message Signing](https://openid.bitbucket.io/fapi/fapi-2_0-message-signing.html)

## Setup

Expand Down
2 changes: 2 additions & 0 deletions include/oidcc_provider_configuration.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@
authorization_response_iss_parameter_supported = false :: boolean(),
%% OAuth 2.0 Demonstrating Proof of Possession (DPoP)
dpop_signing_alg_values_supported = undefined :: [binary()] | undefined,
%% RFC 9101 The OAuth 2.0 Authorization Framework: JWT-Secured Authorization Request (JAR)
require_signed_request_object = false :: boolean(),
%% Unknown Fields
extra_fields = #{} :: #{binary() => term()}
}
Expand Down
39 changes: 39 additions & 0 deletions lib/oidcc/client_context.ex
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,45 @@ defmodule Oidcc.ClientContext do
|> record_to_struct()
end

@doc """
Apply OpenID Connect / OAuth2 Profiles to the context

See `:oidcc_client_context.apply_profiles/2` for more.

## Examples

iex> {:ok, _pid} =
...> Oidcc.ProviderConfiguration.Worker.start_link(%{
...> issuer: "https://accounts.google.com",
...> name: __MODULE__.GoogleConfigProvider
...> })
...>
...> {:ok, client_context} =
...> Oidcc.ClientContext.from_configuration_worker(
...> __MODULE__.GoogleConfigProvider,
...> "client_id",
...> "client_Secret"
...> )
...>
...> {:ok, %Oidcc.ClientContext{}, %{}} =
...> Oidcc.ClientContext.apply_profiles(
...> client_context,
...> %{profiles: [:fapi2_message_signing]}
...> )
"""
@doc since: "3.2.0"
@spec apply_profiles(t(), :oidcc_profile.opts()) ::
{:ok, t(), :oidcc_profile.opts_no_profiles()} | {:error, :oidcc_client_context.error()}
def apply_profiles(client_context, opts) do
case :oidcc_client_context.apply_profiles(struct_to_record(client_context), opts) do
{:ok, context_record, opts} ->
{:ok, record_to_struct(context_record), opts}

{:error, reason} ->
{:error, reason}
end
end

@impl Oidcc.RecordStruct
def record_to_struct(record) do
record
Expand Down
1 change: 1 addition & 0 deletions lib/oidcc/provider_configuration.ex
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ defmodule Oidcc.ProviderConfiguration do
authorization_encryption_alg_values_supported: [String.t()] | :undefined,
authorization_encryption_enc_values_supported: [String.t()] | :undefined,
dpop_signing_alg_values_supported: [String.t()] | :undefined,
require_signed_request_object: boolean(),
extra_fields: %{String.t() => term()}
}

Expand Down
106 changes: 106 additions & 0 deletions priv/test/fixtures/fapi2-metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
{
"issuer": "https://my.provider",
"authorization_endpoint": "https://my.provider/auth",
"registration_endpoint": "https://my.provider/register",
"device_authorization_endpoint": "https://my.provider/device/code",
"token_endpoint": "https://my.provider/token",
"introspection_endpoint": "https://my.provider/introspection",
"userinfo_endpoint": "https://my.provider/userinfo",
"revocation_endpoint": "https://my.provider/revoke",
"jwks_uri": "https://my.provider/jwks",
"response_types_supported": [
"code",
"token",
"id_token",
"code token",
"code id_token",
"token id_token",
"code token id_token",
"none"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"none",
"HS256",
"RS256",
"EdDSA"
],
"scopes_supported": [
"openid",
"email",
"profile"
],
"token_endpoint_auth_methods_supported": [
"client_secret_post",
"client_secret_basic",
"private_key_jwt",
"unsupporeted_auth"
],
"claims_supported": [
"aud",
"email",
"email_verified",
"exp",
"family_name",
"given_name",
"iat",
"iss",
"locale",
"name",
"picture",
"sub"
],
"code_challenge_methods_supported": [
"plain",
"S256"
],
"grant_types_supported": [
"authorization_code",
"refresh_token",
"urn:ietf:params:oauth:grant-type:device_code",
"urn:ietf:params:oauth:grant-type:jwt-bearer"
],
"userinfo_signing_alg_values_supported": [
"none",
"HS256",
"RS256",
"RS384",
"RS512",
"PS256",
"PS384",
"PS512",
"ES256",
"ES256K",
"ES384",
"ES512",
"EdDSA"
],
"userinfo_encryption_alg_values_supported": [
"RSA1_5",
"RSA-OAEP",
"RSA-OAEP-256",
"RSA-OAEP-384",
"RSA-OAEP-512",
"ECDH-ES",
"ECDH-ES+A128KW",
"ECDH-ES+A192KW",
"ECDH-ES+A256KW",
"A128KW",
"A192KW",
"A256KW",
"A128GCMKW",
"A192GCMKW",
"A256GCMKW",
"dir"
],
"userinfo_encryption_enc_values_supported": [
"A128CBC-HS256",
"A192CBC-HS384",
"A256CBC-HS512",
"A128GCM",
"A192GCM",
"A256GCM"
]
}
3 changes: 3 additions & 0 deletions priv/test/fixtures/jwk-ed25519.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIK2JHugMJkquFakgWVf2McF16O6Vkf3rA3PAcutRFmbK
-----END PRIVATE KEY-----
48 changes: 32 additions & 16 deletions src/oidcc.erl
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,16 @@ when
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),
{ClientContextOpts, OtherOpts0} = extract_client_context_opts(Opts),
maybe
{ok, ClientContext} ?=
{ok, ClientContext0} ?=
oidcc_client_context:from_configuration_worker(
ProviderConfigurationWorkerName,
ClientId,
ClientSecret,
ClientContextOpts
),
{ok, ClientContext, OtherOpts} = oidcc_profile:apply_profiles(ClientContext0, OtherOpts0),
oidcc_authorization:create_redirect_url(ClientContext, OtherOpts)
end.

Expand Down Expand Up @@ -128,16 +129,19 @@ retrieve_token(
{ClientContextOpts, OtherOpts} = extract_client_context_opts(Opts),

RefreshJwksFun = oidcc_jwt_util:refresh_jwks_fun(ProviderConfigurationWorkerName),
OptsWithRefresh = maps_put_new(refresh_jwks, RefreshJwksFun, OtherOpts),
OptsWithRefresh0 = maps_put_new(refresh_jwks, RefreshJwksFun, OtherOpts),

maybe
{ok, ClientContext} ?=
{ok, ClientContext0} ?=
oidcc_client_context:from_configuration_worker(
ProviderConfigurationWorkerName,
ClientId,
ClientSecret,
ClientContextOpts
),
{ok, ClientContext, OptsWithRefresh} = oidcc_profile:apply_profiles(
ClientContext0, OptsWithRefresh0
),
oidcc_token:retrieve(AuthCode, ClientContext, OptsWithRefresh)
end.

Expand Down Expand Up @@ -190,16 +194,17 @@ retrieve_userinfo(
ClientSecret,
Opts
) ->
{ClientContextOpts, OtherOpts} = extract_client_context_opts(Opts),
{ClientContextOpts, OtherOpts0} = extract_client_context_opts(Opts),

maybe
{ok, ClientContext} ?=
{ok, ClientContext0} ?=
oidcc_client_context:from_configuration_worker(
ProviderConfigurationWorkerName,
ClientId,
ClientSecret,
ClientContextOpts
),
{ok, ClientContext, OtherOpts} = oidcc_profile:apply_profiles(ClientContext0, OtherOpts0),
oidcc_userinfo:retrieve(Token, ClientContext, OtherOpts)
end.

Expand Down Expand Up @@ -260,16 +265,19 @@ refresh_token(
{ClientContextOpts, OtherOpts} = extract_client_context_opts(Opts),

RefreshJwksFun = oidcc_jwt_util:refresh_jwks_fun(ProviderConfigurationWorkerName),
OptsWithRefresh = maps_put_new(refresh_jwks, RefreshJwksFun, OtherOpts),
OptsWithRefresh0 = maps_put_new(refresh_jwks, RefreshJwksFun, OtherOpts),

maybe
{ok, ClientContext} ?=
{ok, ClientContext0} ?=
oidcc_client_context:from_configuration_worker(
ProviderConfigurationWorkerName,
ClientId,
ClientSecret,
ClientContextOpts
),
{ok, ClientContext, OptsWithRefresh} = oidcc_profile:apply_profiles(
ClientContext0, OptsWithRefresh0
),
oidcc_token:refresh(RefreshToken, ClientContext, OptsWithRefresh)
end.

Expand Down Expand Up @@ -314,16 +322,17 @@ introspect_token(
ClientSecret,
Opts
) ->
{ClientContextOpts, OtherOpts} = extract_client_context_opts(Opts),
{ClientContextOpts, OtherOpts0} = extract_client_context_opts(Opts),

maybe
{ok, ClientContext} ?=
{ok, ClientContext0} ?=
oidcc_client_context:from_configuration_worker(
ProviderConfigurationWorkerName,
ClientId,
ClientSecret,
ClientContextOpts
),
{ok, ClientContext, OtherOpts} = oidcc_profile:apply_profiles(ClientContext0, OtherOpts0),
oidcc_token_introspection:introspect(Token, ClientContext, OtherOpts)
end.

Expand Down Expand Up @@ -371,16 +380,19 @@ jwt_profile_token(Subject, ProviderConfigurationWorkerName, ClientId, ClientSecr
{ClientContextOpts, OtherOpts} = extract_client_context_opts(Opts),

RefreshJwksFun = oidcc_jwt_util:refresh_jwks_fun(ProviderConfigurationWorkerName),
OptsWithRefresh = maps_put_new(refresh_jwks, RefreshJwksFun, OtherOpts),
OptsWithRefresh0 = maps_put_new(refresh_jwks, RefreshJwksFun, OtherOpts),

maybe
{ok, ClientContext} ?=
{ok, ClientContext0} ?=
oidcc_client_context:from_configuration_worker(
ProviderConfigurationWorkerName,
ClientId,
ClientSecret,
ClientContextOpts
),
{ok, ClientContext, OptsWithRefresh} = oidcc_profile:apply_profiles(
ClientContext0, OptsWithRefresh0
),
oidcc_token:jwt_profile(Subject, ClientContext, Jwk, OptsWithRefresh)
end.

Expand Down Expand Up @@ -415,16 +427,19 @@ client_credentials_token(ProviderConfigurationWorkerName, ClientId, ClientSecret
{ClientContextOpts, OtherOpts} = extract_client_context_opts(Opts),

RefreshJwksFun = oidcc_jwt_util:refresh_jwks_fun(ProviderConfigurationWorkerName),
OptsWithRefresh = maps_put_new(refresh_jwks, RefreshJwksFun, OtherOpts),
OptsWithRefresh0 = maps_put_new(refresh_jwks, RefreshJwksFun, OtherOpts),

maybe
{ok, ClientContext} ?=
{ok, ClientContext0} ?=
oidcc_client_context:from_configuration_worker(
ProviderConfigurationWorkerName,
ClientId,
ClientSecret,
ClientContextOpts
),
{ok, ClientContext, OptsWithRefresh} = oidcc_profile:apply_profiles(
ClientContext0, OptsWithRefresh0
),
oidcc_token:client_credentials(ClientContext, OptsWithRefresh)
end.

Expand Down Expand Up @@ -464,16 +479,17 @@ when
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),
{ClientContextOpts, OtherOpts0} = extract_client_context_opts(Opts),

maybe
{ok, ClientContext} ?=
{ok, ClientContext0} ?=
oidcc_client_context:from_configuration_worker(
ProviderConfigurationWorkerName,
ClientId,
unauthenticated,
ClientContextOpts
),
{ok, ClientContext, OtherOpts} = oidcc_profile:apply_profiles(ClientContext0, OtherOpts0),
oidcc_logout:initiate_url(Token, ClientContext, OtherOpts)
end.

Expand Down
Loading