Skip to content

Commit

Permalink
wip: FAPI2 profile support
Browse files Browse the repository at this point in the history
Ref: #316
  • Loading branch information
paulswartz committed Dec 23, 2023
1 parent 4cbce04 commit 81c7234
Show file tree
Hide file tree
Showing 7 changed files with 466 additions and 48 deletions.
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
63 changes: 39 additions & 24 deletions src/oidcc_authorization.erl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
pkce_verifier => binary(),
redirect_uri := uri_string:uri_string(),
url_extension => oidcc_http_util:query_params()
}.
}
| oidcc_profile:opts().
%% Configure authorization redirect url
%%
%% See [https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest]
Expand All @@ -43,7 +44,11 @@
%% </ul>

-type error() ::
{grant_type_not_supported, authorization_code} | par_required | oidcc_http_util:error().
{grant_type_not_supported, authorization_code}
| par_required
| pkce_verifier_required
| no_supported_code_challenge
| oidcc_http_util:error().

%% @doc
%% Create Auth Redirect URL
Expand Down Expand Up @@ -104,31 +109,35 @@ redirect_params(#oidcc_client_context{client_id = ClientId} = ClientContext, Opt
],
QueryParams1 = maybe_append(<<"state">>, maps:get(state, Opts, undefined), QueryParams),
QueryParams2 = maybe_append(<<"nonce">>, maps:get(nonce, Opts, undefined), QueryParams1),
QueryParams3 = append_code_challenge(
maps:get(pkce_verifier, Opts, none), QueryParams2, ClientContext
),
QueryParams4 = oidcc_scope:query_append_scope(
maps:get(scopes, Opts, [openid]), QueryParams3
),
QueryParams5 = maybe_append_dpop_jkt(QueryParams4, ClientContext),
QueryParams6 = attempt_request_object(QueryParams5, ClientContext),
attempt_par(QueryParams6, ClientContext, Opts).
maybe
{ok, QueryParams3} ?=
append_code_challenge(
Opts, QueryParams2, ClientContext
),
QueryParams4 = oidcc_scope:query_append_scope(
maps:get(scopes, Opts, [openid]), QueryParams3
),
QueryParams5 = maybe_append_dpop_jkt(QueryParams4, ClientContext),
QueryParams6 = attempt_request_object(QueryParams5, ClientContext),
attempt_par(QueryParams6, ClientContext, Opts)
end.

-spec append_code_challenge(PkceVerifier, QueryParams, ClientContext) ->
oidcc_http_util:query_params()
-spec append_code_challenge(Opts, QueryParams, ClientContext) ->
{ok, oidcc_http_util:query_params()} | {error, error()}
when
PkceVerifier :: binary() | none,
Opts :: opts(),
QueryParams :: oidcc_http_util:query_params(),
ClientContext :: oidcc_client_context:t().
append_code_challenge(none, QueryParams, _ClientContext) ->
QueryParams;
append_code_challenge(CodeVerifier, QueryParams, ClientContext) ->
append_code_challenge(#{pkce_verifier := CodeVerifier} = Opts, QueryParams, ClientContext) ->
#oidcc_client_context{provider_configuration = ProviderConfiguration} = ClientContext,
#oidcc_provider_configuration{code_challenge_methods_supported = CodeChallengeMethodsSupported} =
ProviderConfiguration,
RequirePkce = maps:get(require_pkce, Opts, false),
case CodeChallengeMethodsSupported of
undefined when RequirePkce =:= true ->
{error, pkce_verifier_required};
undefined ->
QueryParams;
{ok, QueryParams};
Methods when is_list(Methods) ->
case
{
Expand All @@ -140,21 +149,27 @@ append_code_challenge(CodeVerifier, QueryParams, ClientContext) ->
CodeChallenge = base64:encode(crypto:hash(sha256, CodeVerifier), #{
mode => urlsafe, padding => false
}),
[
{ok, [
{<<"code_challenge">>, CodeChallenge},
{<<"code_challenge_method">>, <<"S256">>}
| QueryParams
];
]};
{false, true} ->
[
{ok, [
{<<"code_challenge">>, CodeVerifier},
{<<"code_challenge_method">>, <<"plain">>}
| QueryParams
];
]};
{false, false} when RequirePkce =:= true ->
{error, no_supported_code_challenge};
{false, false} ->
QueryParams
{ok, QueryParams}
end
end.
end;
append_code_challenge(#{require_pkce := true}, _QueryParams, _ClientContext) ->
{error, pkce_verifier_required};
append_code_challenge(_Opts, QueryParams, _ClientContext) ->
{ok, QueryParams}.

-spec maybe_append(Key, Value, QueryParams) -> QueryParams when
Key :: unicode:chardata(),
Expand Down
Loading

0 comments on commit 81c7234

Please sign in to comment.