feat(oidc): per-client pkce enforcement policy (#4692)
This implements a per-client PKCE enforcement policy with the ability to enforce that it's used, and the specific challenge mode.pull/4694/head
parent
1e86cb9ca8
commit
adaf069eab
|
@ -1394,15 +1394,9 @@ notifier:
|
||||||
## Sets the client to public. This should typically not be set, please see the documentation for usage.
|
## Sets the client to public. This should typically not be set, please see the documentation for usage.
|
||||||
# public: false
|
# public: false
|
||||||
|
|
||||||
## The policy to require for this client; one_factor or two_factor.
|
## Redirect URI's specifies a list of valid case-sensitive callbacks for this client.
|
||||||
# authorization_policy: two_factor
|
# redirect_uris:
|
||||||
|
# - https://oidc.example.com:8080/oauth2/callback
|
||||||
## The consent mode controls how consent is obtained.
|
|
||||||
# consent_mode: auto
|
|
||||||
|
|
||||||
## This value controls the duration a consent on this client remains remembered when the consent mode is
|
|
||||||
## configured as 'auto' or 'pre-configured'.
|
|
||||||
# pre_configured_consent_duration: 1w
|
|
||||||
|
|
||||||
## Audience this client is allowed to request.
|
## Audience this client is allowed to request.
|
||||||
# audience: []
|
# audience: []
|
||||||
|
@ -1414,10 +1408,6 @@ notifier:
|
||||||
# - email
|
# - email
|
||||||
# - profile
|
# - profile
|
||||||
|
|
||||||
## Redirect URI's specifies a list of valid case-sensitive callbacks for this client.
|
|
||||||
# redirect_uris:
|
|
||||||
# - https://oidc.example.com:8080/oauth2/callback
|
|
||||||
|
|
||||||
## Grant Types configures which grants this client can obtain.
|
## Grant Types configures which grants this client can obtain.
|
||||||
## It's not recommended to define this unless you know what you're doing.
|
## It's not recommended to define this unless you know what you're doing.
|
||||||
# grant_types:
|
# grant_types:
|
||||||
|
@ -1435,6 +1425,23 @@ notifier:
|
||||||
# - query
|
# - query
|
||||||
# - fragment
|
# - fragment
|
||||||
|
|
||||||
|
## The policy to require for this client; one_factor or two_factor.
|
||||||
|
# authorization_policy: two_factor
|
||||||
|
|
||||||
|
## Enforces the use of PKCE for this client when set to true.
|
||||||
|
# enforce_pkce: false
|
||||||
|
|
||||||
|
## Enforces the use of PKCE for this client when configured, and enforces the specified challenge method.
|
||||||
|
## Options are 'plain' and 'S256'.
|
||||||
|
# pkce_challenge_method: S256
|
||||||
|
|
||||||
## The algorithm used to sign userinfo endpoint responses for this client, either none or RS256.
|
## The algorithm used to sign userinfo endpoint responses for this client, either none or RS256.
|
||||||
# userinfo_signing_algorithm: none
|
# userinfo_signing_algorithm: none
|
||||||
|
|
||||||
|
## The consent mode controls how consent is obtained.
|
||||||
|
# consent_mode: auto
|
||||||
|
|
||||||
|
## This value controls the duration a consent on this client remains remembered when the consent mode is
|
||||||
|
## configured as 'auto' or 'pre-configured'.
|
||||||
|
# pre_configured_consent_duration: 1w
|
||||||
...
|
...
|
||||||
|
|
|
@ -404,12 +404,92 @@ useful for SPA's and CLI tools. This option requires setting the [client secret]
|
||||||
In addition to the standard rules for redirect URIs, public clients can use the `urn:ietf:wg:oauth:2.0:oob` redirect
|
In addition to the standard rules for redirect URIs, public clients can use the `urn:ietf:wg:oauth:2.0:oob` redirect
|
||||||
URI.
|
URI.
|
||||||
|
|
||||||
|
#### redirect_uris
|
||||||
|
|
||||||
|
{{< confkey type="list(string)" required="yes" >}}
|
||||||
|
|
||||||
|
A list of valid callback URIs this client will redirect to. All other callbacks will be considered unsafe. The URIs are
|
||||||
|
case-sensitive and they differ from application to application - the community has provided
|
||||||
|
[a list of URL´s for common applications](../../integration/openid-connect/introduction.md).
|
||||||
|
|
||||||
|
Some restrictions that have been placed on clients and
|
||||||
|
their redirect URIs are as follows:
|
||||||
|
|
||||||
|
1. If a client attempts to authorize with Authelia and its redirect URI is not listed in the client configuration the
|
||||||
|
attempt to authorize will fail and an error will be generated.
|
||||||
|
2. The redirect URIs are case-sensitive.
|
||||||
|
3. The URI must include a scheme and that scheme must be one of `http` or `https`.
|
||||||
|
4. The client can ignore rule 3 and use `urn:ietf:wg:oauth:2.0:oob` if it is a [public](#public) client type.
|
||||||
|
|
||||||
|
#### audience
|
||||||
|
|
||||||
|
{{< confkey type="list(string)" required="no" >}}
|
||||||
|
|
||||||
|
A list of audiences this client is allowed to request.
|
||||||
|
|
||||||
|
#### scopes
|
||||||
|
|
||||||
|
{{< confkey type="list(string)" default="openid, groups, profile, email" required="no" >}}
|
||||||
|
|
||||||
|
A list of scopes to allow this client to consume. See
|
||||||
|
[scope definitions](../../integration/openid-connect/introduction.md#scope-definitions) for more information. The
|
||||||
|
documentation for the application you want to use with Authelia will most-likely provide you with the scopes to allow.
|
||||||
|
|
||||||
|
#### grant_types
|
||||||
|
|
||||||
|
{{< confkey type="list(string)" default="refresh_token, authorization_code" required="no" >}}
|
||||||
|
|
||||||
|
A list of grant types this client can return. *It is recommended that this isn't configured at this time unless you
|
||||||
|
know what you're doing*. Valid options are: `implicit`, `refresh_token`, `authorization_code`, `password`,
|
||||||
|
`client_credentials`.
|
||||||
|
|
||||||
|
#### response_types
|
||||||
|
|
||||||
|
{{< confkey type="list(string)" default="code" required="no" >}}
|
||||||
|
|
||||||
|
A list of response types this client can return. *It is recommended that this isn't configured at this time unless you
|
||||||
|
know what you're doing*. Valid options are: `code`, `code id_token`, `id_token`, `token id_token`, `token`,
|
||||||
|
`token id_token code`.
|
||||||
|
|
||||||
|
#### response_modes
|
||||||
|
|
||||||
|
{{< confkey type="list(string)" default="form_post, query, fragment" required="no" >}}
|
||||||
|
|
||||||
|
A list of response modes this client can return. It is recommended that this isn't configured at this time unless you
|
||||||
|
know what you're doing. Potential values are `form_post`, `query`, and `fragment`.
|
||||||
|
|
||||||
#### authorization_policy
|
#### authorization_policy
|
||||||
|
|
||||||
{{< confkey type="string" default="two_factor" required="no" >}}
|
{{< confkey type="string" default="two_factor" required="no" >}}
|
||||||
|
|
||||||
The authorization policy for this client: either `one_factor` or `two_factor`.
|
The authorization policy for this client: either `one_factor` or `two_factor`.
|
||||||
|
|
||||||
|
#### enforce_pkce
|
||||||
|
|
||||||
|
{{< confkey type="bool" default="false" required="no" >}}
|
||||||
|
|
||||||
|
This setting enforces the use of [PKCE] for this individual client. To enforce it for all clients see the global
|
||||||
|
[enforce_pkce](#enforcepkce) setting.
|
||||||
|
|
||||||
|
#### pkce_challenge_method
|
||||||
|
|
||||||
|
{{< confkey type="string" default="" required="no" >}}
|
||||||
|
|
||||||
|
This setting enforces the use of the specified [PKCE] challenge method for this individual client. This setting also
|
||||||
|
effectively enables the [enforce_pkce](#enforcepkce-1) option for this client.
|
||||||
|
|
||||||
|
Valid values are an empty string, `plain`, or `S256`. It should be noted that `S256` is strongly recommended if the
|
||||||
|
relying party supports it.
|
||||||
|
|
||||||
|
#### userinfo_signing_algorithm
|
||||||
|
|
||||||
|
{{< confkey type="string" default="none" required="no" >}}
|
||||||
|
|
||||||
|
The algorithm used to sign the userinfo endpoint responses. This can either be `none` or `RS256`.
|
||||||
|
|
||||||
|
See the [integration guide](../../integration/openid-connect/introduction.md#user-information-signing-algorithm) for
|
||||||
|
more information.
|
||||||
|
|
||||||
#### consent_mode
|
#### consent_mode
|
||||||
|
|
||||||
{{< confkey type="string" default="auto" required="no" >}}
|
{{< confkey type="string" default="auto" required="no" >}}
|
||||||
|
@ -442,69 +522,6 @@ match exactly with the granted scopes/audience.
|
||||||
|
|
||||||
[consent_mode]: #consentmode
|
[consent_mode]: #consentmode
|
||||||
|
|
||||||
#### audience
|
|
||||||
|
|
||||||
{{< confkey type="list(string)" required="no" >}}
|
|
||||||
|
|
||||||
A list of audiences this client is allowed to request.
|
|
||||||
|
|
||||||
#### scopes
|
|
||||||
|
|
||||||
{{< confkey type="list(string)" default="openid, groups, profile, email" required="no" >}}
|
|
||||||
|
|
||||||
A list of scopes to allow this client to consume. See
|
|
||||||
[scope definitions](../../integration/openid-connect/introduction.md#scope-definitions) for more information. The
|
|
||||||
documentation for the application you want to use with Authelia will most-likely provide you with the scopes to allow.
|
|
||||||
|
|
||||||
#### redirect_uris
|
|
||||||
|
|
||||||
{{< confkey type="list(string)" required="yes" >}}
|
|
||||||
|
|
||||||
A list of valid callback URIs this client will redirect to. All other callbacks will be considered unsafe. The URIs are
|
|
||||||
case-sensitive and they differ from application to application - the community has provided
|
|
||||||
[a list of URL´s for common applications](../../integration/openid-connect/introduction.md).
|
|
||||||
|
|
||||||
Some restrictions that have been placed on clients and
|
|
||||||
their redirect URIs are as follows:
|
|
||||||
|
|
||||||
1. If a client attempts to authorize with Authelia and its redirect URI is not listed in the client configuration the
|
|
||||||
attempt to authorize will fail and an error will be generated.
|
|
||||||
2. The redirect URIs are case-sensitive.
|
|
||||||
3. The URI must include a scheme and that scheme must be one of `http` or `https`.
|
|
||||||
4. The client can ignore rule 3 and use `urn:ietf:wg:oauth:2.0:oob` if it is a [public](#public) client type.
|
|
||||||
|
|
||||||
#### grant_types
|
|
||||||
|
|
||||||
{{< confkey type="list(string)" default="refresh_token, authorization_code" required="no" >}}
|
|
||||||
|
|
||||||
A list of grant types this client can return. *It is recommended that this isn't configured at this time unless you
|
|
||||||
know what you're doing*. Valid options are: `implicit`, `refresh_token`, `authorization_code`, `password`,
|
|
||||||
`client_credentials`.
|
|
||||||
|
|
||||||
#### response_types
|
|
||||||
|
|
||||||
{{< confkey type="list(string)" default="code" required="no" >}}
|
|
||||||
|
|
||||||
A list of response types this client can return. *It is recommended that this isn't configured at this time unless you
|
|
||||||
know what you're doing*. Valid options are: `code`, `code id_token`, `id_token`, `token id_token`, `token`,
|
|
||||||
`token id_token code`.
|
|
||||||
|
|
||||||
#### response_modes
|
|
||||||
|
|
||||||
{{< confkey type="list(string)" default="form_post, query, fragment" required="no" >}}
|
|
||||||
|
|
||||||
A list of response modes this client can return. It is recommended that this isn't configured at this time unless you
|
|
||||||
know what you're doing. Potential values are `form_post`, `query`, and `fragment`.
|
|
||||||
|
|
||||||
#### userinfo_signing_algorithm
|
|
||||||
|
|
||||||
{{< confkey type="string" default="none" required="no" >}}
|
|
||||||
|
|
||||||
The algorithm used to sign the userinfo endpoint responses. This can either be `none` or `RS256`.
|
|
||||||
|
|
||||||
See the [integration guide](../../integration/openid-connect/introduction.md#user-information-signing-algorithm) for
|
|
||||||
more information.
|
|
||||||
|
|
||||||
## Integration
|
## Integration
|
||||||
|
|
||||||
To integrate Authelia's [OpenID Connect] implementation with a relying party please see the
|
To integrate Authelia's [OpenID Connect] implementation with a relying party please see the
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
title: "Templating"
|
title: "Templating"
|
||||||
description: "A reference guide on the templates system"
|
description: "A reference guide on the templates system"
|
||||||
lead: "This section contains reference documentation for Authelia's templating capabilities."
|
lead: "This section contains reference documentation for Authelia's templating capabilities."
|
||||||
date: 2022-12-23T18:31:05+11:00
|
date: 2022-12-23T21:58:54+11:00
|
||||||
draft: false
|
draft: false
|
||||||
images: []
|
images: []
|
||||||
menu:
|
menu:
|
||||||
|
|
|
@ -64,7 +64,7 @@ Feature List:
|
||||||
|
|
||||||
Feature List:
|
Feature List:
|
||||||
|
|
||||||
* [Proof Key Code Exchange (PKCE)](https://www.rfc-editor.org/rfc/rfc7636.html) for Authorization Code Flow
|
* [Proof Key Code Exchange (PKCE)] for Authorization Code Flow
|
||||||
* Claims:
|
* Claims:
|
||||||
* `preferred_username` - sending the username in this claim instead of the `sub` claim.
|
* `preferred_username` - sending the username in this claim instead of the `sub` claim.
|
||||||
|
|
||||||
|
@ -115,8 +115,8 @@ Feature List:
|
||||||
|
|
||||||
{{< roadmap-status stage="in-progress" version="v4.38.0" >}}
|
{{< roadmap-status stage="in-progress" version="v4.38.0" >}}
|
||||||
|
|
||||||
|
|
||||||
* [OAuth 2.0 Pushed Authorization Requests](https://www.rfc-editor.org/rfc/rfc9126.html)
|
* [OAuth 2.0 Pushed Authorization Requests](https://www.rfc-editor.org/rfc/rfc9126.html)
|
||||||
|
* Per-Client [Proof Key Code Exchange (PKCE)] Policy
|
||||||
|
|
||||||
### Beta 7
|
### Beta 7
|
||||||
|
|
||||||
|
@ -219,3 +219,4 @@ The `preferred_username` claim was missing and was fixed.
|
||||||
[OpenID Connect Core (Subject Identifier Types)]: https://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
|
[OpenID Connect Core (Subject Identifier Types)]: https://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
|
||||||
[OpenID Connect Core (Pairwise Identifier Algorithm)]: https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg
|
[OpenID Connect Core (Pairwise Identifier Algorithm)]: https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg
|
||||||
[OpenID Connect Core (Mandatory to Implement Features for All OpenID Providers)]: https://openid.net/specs/openid-connect-core-1_0.html#ServerMTI
|
[OpenID Connect Core (Mandatory to Implement Features for All OpenID Providers)]: https://openid.net/specs/openid-connect-core-1_0.html#ServerMTI
|
||||||
|
[Proof Key Code Exchange (PKCE)]: https://www.rfc-editor.org/rfc/rfc7636.html
|
||||||
|
|
|
@ -1394,15 +1394,9 @@ notifier:
|
||||||
## Sets the client to public. This should typically not be set, please see the documentation for usage.
|
## Sets the client to public. This should typically not be set, please see the documentation for usage.
|
||||||
# public: false
|
# public: false
|
||||||
|
|
||||||
## The policy to require for this client; one_factor or two_factor.
|
## Redirect URI's specifies a list of valid case-sensitive callbacks for this client.
|
||||||
# authorization_policy: two_factor
|
# redirect_uris:
|
||||||
|
# - https://oidc.example.com:8080/oauth2/callback
|
||||||
## The consent mode controls how consent is obtained.
|
|
||||||
# consent_mode: auto
|
|
||||||
|
|
||||||
## This value controls the duration a consent on this client remains remembered when the consent mode is
|
|
||||||
## configured as 'auto' or 'pre-configured'.
|
|
||||||
# pre_configured_consent_duration: 1w
|
|
||||||
|
|
||||||
## Audience this client is allowed to request.
|
## Audience this client is allowed to request.
|
||||||
# audience: []
|
# audience: []
|
||||||
|
@ -1414,10 +1408,6 @@ notifier:
|
||||||
# - email
|
# - email
|
||||||
# - profile
|
# - profile
|
||||||
|
|
||||||
## Redirect URI's specifies a list of valid case-sensitive callbacks for this client.
|
|
||||||
# redirect_uris:
|
|
||||||
# - https://oidc.example.com:8080/oauth2/callback
|
|
||||||
|
|
||||||
## Grant Types configures which grants this client can obtain.
|
## Grant Types configures which grants this client can obtain.
|
||||||
## It's not recommended to define this unless you know what you're doing.
|
## It's not recommended to define this unless you know what you're doing.
|
||||||
# grant_types:
|
# grant_types:
|
||||||
|
@ -1435,6 +1425,23 @@ notifier:
|
||||||
# - query
|
# - query
|
||||||
# - fragment
|
# - fragment
|
||||||
|
|
||||||
|
## The policy to require for this client; one_factor or two_factor.
|
||||||
|
# authorization_policy: two_factor
|
||||||
|
|
||||||
|
## Enforces the use of PKCE for this client when set to true.
|
||||||
|
# enforce_pkce: false
|
||||||
|
|
||||||
|
## Enforces the use of PKCE for this client when configured, and enforces the specified challenge method.
|
||||||
|
## Options are 'plain' and 'S256'.
|
||||||
|
# pkce_challenge_method: S256
|
||||||
|
|
||||||
## The algorithm used to sign userinfo endpoint responses for this client, either none or RS256.
|
## The algorithm used to sign userinfo endpoint responses for this client, either none or RS256.
|
||||||
# userinfo_signing_algorithm: none
|
# userinfo_signing_algorithm: none
|
||||||
|
|
||||||
|
## The consent mode controls how consent is obtained.
|
||||||
|
# consent_mode: auto
|
||||||
|
|
||||||
|
## This value controls the duration a consent on this client remains remembered when the consent mode is
|
||||||
|
## configured as 'auto' or 'pre-configured'.
|
||||||
|
# pre_configured_consent_duration: 1w
|
||||||
...
|
...
|
||||||
|
|
|
@ -57,10 +57,13 @@ type OpenIDConnectClientConfiguration struct {
|
||||||
ResponseTypes []string `koanf:"response_types"`
|
ResponseTypes []string `koanf:"response_types"`
|
||||||
ResponseModes []string `koanf:"response_modes"`
|
ResponseModes []string `koanf:"response_modes"`
|
||||||
|
|
||||||
UserinfoSigningAlgorithm string `koanf:"userinfo_signing_algorithm"`
|
|
||||||
|
|
||||||
Policy string `koanf:"authorization_policy"`
|
Policy string `koanf:"authorization_policy"`
|
||||||
|
|
||||||
|
EnforcePKCE bool `koanf:"enforce_pkce"`
|
||||||
|
|
||||||
|
PKCEChallengeMethod string `koanf:"pkce_challenge_method"`
|
||||||
|
UserinfoSigningAlgorithm string `koanf:"userinfo_signing_algorithm"`
|
||||||
|
|
||||||
ConsentMode string `koanf:"consent_mode"`
|
ConsentMode string `koanf:"consent_mode"`
|
||||||
ConsentPreConfiguredDuration *time.Duration `koanf:"pre_configured_consent_duration"`
|
ConsentPreConfiguredDuration *time.Duration `koanf:"pre_configured_consent_duration"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,8 +43,10 @@ var Keys = []string{
|
||||||
"identity_providers.oidc.clients[].grant_types",
|
"identity_providers.oidc.clients[].grant_types",
|
||||||
"identity_providers.oidc.clients[].response_types",
|
"identity_providers.oidc.clients[].response_types",
|
||||||
"identity_providers.oidc.clients[].response_modes",
|
"identity_providers.oidc.clients[].response_modes",
|
||||||
"identity_providers.oidc.clients[].userinfo_signing_algorithm",
|
|
||||||
"identity_providers.oidc.clients[].authorization_policy",
|
"identity_providers.oidc.clients[].authorization_policy",
|
||||||
|
"identity_providers.oidc.clients[].enforce_pkce",
|
||||||
|
"identity_providers.oidc.clients[].pkce_challenge_method",
|
||||||
|
"identity_providers.oidc.clients[].userinfo_signing_algorithm",
|
||||||
"identity_providers.oidc.clients[].consent_mode",
|
"identity_providers.oidc.clients[].consent_mode",
|
||||||
"identity_providers.oidc.clients[].pre_configured_consent_duration",
|
"identity_providers.oidc.clients[].pre_configured_consent_duration",
|
||||||
"authentication_backend.password_reset.disable",
|
"authentication_backend.password_reset.disable",
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"github.com/go-webauthn/webauthn/protocol"
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/oidc"
|
"github.com/authelia/authelia/v4/internal/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -172,6 +171,8 @@ const (
|
||||||
"invalid value: redirect uri '%s' must have the scheme but it is absent"
|
"invalid value: redirect uri '%s' must have the scheme but it is absent"
|
||||||
errFmtOIDCClientInvalidPolicy = "identity_providers: oidc: client '%s': option 'policy' must be 'one_factor' " +
|
errFmtOIDCClientInvalidPolicy = "identity_providers: oidc: client '%s': option 'policy' must be 'one_factor' " +
|
||||||
"or 'two_factor' but it is configured as '%s'"
|
"or 'two_factor' but it is configured as '%s'"
|
||||||
|
errFmtOIDCClientInvalidPKCEChallengeMethod = "identity_providers: oidc: client '%s': option 'pkce_challenge_method' must be 'plain' " +
|
||||||
|
"or 'S256' but it is configured as '%s'"
|
||||||
errFmtOIDCClientInvalidConsentMode = "identity_providers: oidc: client '%s': consent: option 'mode' must be one of " +
|
errFmtOIDCClientInvalidConsentMode = "identity_providers: oidc: client '%s': consent: option 'mode' must be one of " +
|
||||||
"'%s' but it is configured as '%s'"
|
"'%s' but it is configured as '%s'"
|
||||||
errFmtOIDCClientInvalidEntry = "identity_providers: oidc: client '%s': option '%s' must only have the values " +
|
errFmtOIDCClientInvalidEntry = "identity_providers: oidc: client '%s': option '%s' must only have the values " +
|
||||||
|
|
|
@ -175,6 +175,13 @@ func validateOIDCClients(config *schema.OpenIDConnectConfiguration, val *schema.
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidPolicy, client.ID, client.Policy))
|
val.Push(fmt.Errorf(errFmtOIDCClientInvalidPolicy, client.ID, client.Policy))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch client.PKCEChallengeMethod {
|
||||||
|
case "", "plain", "S256":
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
val.Push(fmt.Errorf(errFmtOIDCClientInvalidPKCEChallengeMethod, client.ID, client.PKCEChallengeMethod))
|
||||||
|
}
|
||||||
|
|
||||||
validateOIDCClientConsentMode(c, config, val)
|
validateOIDCClientConsentMode(c, config, val)
|
||||||
validateOIDCClientSectorIdentifier(client, val)
|
validateOIDCClientSectorIdentifier(client, val)
|
||||||
validateOIDCClientScopes(c, config, val)
|
validateOIDCClientScopes(c, config, val)
|
||||||
|
|
|
@ -331,6 +331,40 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
|
||||||
fmt.Sprintf(errFmtOIDCClientInvalidConsentMode, "client-bad-consent-mode", strings.Join(append(validOIDCClientConsentModes, "auto"), "', '"), "cap"),
|
fmt.Sprintf(errFmtOIDCClientInvalidConsentMode, "client-bad-consent-mode", strings.Join(append(validOIDCClientConsentModes, "auto"), "', '"), "cap"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "InvalidPKCEChallengeMethod",
|
||||||
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
|
{
|
||||||
|
ID: "client-bad-pkce-mode",
|
||||||
|
Secret: MustDecodeSecret("$plaintext$a-secret"),
|
||||||
|
Policy: policyTwoFactor,
|
||||||
|
RedirectURIs: []string{
|
||||||
|
"https://google.com",
|
||||||
|
},
|
||||||
|
PKCEChallengeMethod: "abc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Errors: []string{
|
||||||
|
fmt.Sprintf(errFmtOIDCClientInvalidPKCEChallengeMethod, "client-bad-pkce-mode", "abc"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "InvalidPKCEChallengeMethodLowerCaseS256",
|
||||||
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
|
{
|
||||||
|
ID: "client-bad-pkce-mode-s256",
|
||||||
|
Secret: MustDecodeSecret("$plaintext$a-secret"),
|
||||||
|
Policy: policyTwoFactor,
|
||||||
|
RedirectURIs: []string{
|
||||||
|
"https://google.com",
|
||||||
|
},
|
||||||
|
PKCEChallengeMethod: "s256",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Errors: []string{
|
||||||
|
fmt.Sprintf(errFmtOIDCClientInvalidPKCEChallengeMethod, "client-bad-pkce-mode-s256", "s256"),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
@ -609,7 +643,7 @@ func TestValidateIdentityProvidersShouldRaiseErrorsOnInvalidClientTypes(t *testi
|
||||||
assert.EqualError(t, validator.Errors()[1], fmt.Sprintf(errFmtOIDCClientRedirectURIPublic, "client-with-bad-redirect-uri", oauth2InstalledApp))
|
assert.EqualError(t, validator.Errors()[1], fmt.Sprintf(errFmtOIDCClientRedirectURIPublic, "client-with-bad-redirect-uri", oauth2InstalledApp))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateIdentityProvidersShouldNotRaiseErrorsOnValidPublicClients(t *testing.T) {
|
func TestValidateIdentityProvidersShouldNotRaiseErrorsOnValidClientOptions(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := &schema.IdentityProvidersConfiguration{
|
config := &schema.IdentityProvidersConfiguration{
|
||||||
OIDC: &schema.OpenIDConnectConfiguration{
|
OIDC: &schema.OpenIDConnectConfiguration{
|
||||||
|
@ -640,6 +674,24 @@ func TestValidateIdentityProvidersShouldNotRaiseErrorsOnValidPublicClients(t *te
|
||||||
"http://127.0.0.1",
|
"http://127.0.0.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ID: "client-with-pkce-mode-plain",
|
||||||
|
Public: true,
|
||||||
|
Policy: "two_factor",
|
||||||
|
RedirectURIs: []string{
|
||||||
|
"https://pkce.com",
|
||||||
|
},
|
||||||
|
PKCEChallengeMethod: "plain",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "client-with-pkce-mode-S256",
|
||||||
|
Public: true,
|
||||||
|
Policy: "two_factor",
|
||||||
|
RedirectURIs: []string{
|
||||||
|
"https://pkce.com",
|
||||||
|
},
|
||||||
|
PKCEChallengeMethod: "S256",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,16 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = client.ValidateAuthorizationPolicy(requester); err != nil {
|
||||||
|
rfc := fosite.ErrorToRFC6749Error(err)
|
||||||
|
|
||||||
|
ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' failed to validate the authorization policy: %s", requester.GetID(), clientID, rfc.WithExposeDebug(true).GetDescription())
|
||||||
|
|
||||||
|
ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
issuer = ctx.RootURL()
|
issuer = ctx.RootURL()
|
||||||
|
|
||||||
userSession := ctx.GetSession()
|
userSession := ctx.GetSession()
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
|
"github.com/ory/x/errorsx"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/authentication"
|
"github.com/authelia/authelia/v4/internal/authentication"
|
||||||
"github.com/authelia/authelia/v4/internal/authorization"
|
"github.com/authelia/authelia/v4/internal/authorization"
|
||||||
|
@ -18,6 +21,10 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client)
|
||||||
SectorIdentifier: config.SectorIdentifier.String(),
|
SectorIdentifier: config.SectorIdentifier.String(),
|
||||||
Public: config.Public,
|
Public: config.Public,
|
||||||
|
|
||||||
|
EnforcePKCE: config.EnforcePKCE || config.PKCEChallengeMethod != "",
|
||||||
|
EnforcePKCEChallengeMethod: config.PKCEChallengeMethod != "",
|
||||||
|
PKCEChallengeMethod: config.PKCEChallengeMethod,
|
||||||
|
|
||||||
Audience: config.Audience,
|
Audience: config.Audience,
|
||||||
Scopes: config.Scopes,
|
Scopes: config.Scopes,
|
||||||
RedirectURIs: config.RedirectURIs,
|
RedirectURIs: config.RedirectURIs,
|
||||||
|
@ -39,6 +46,29 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client)
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateAuthorizationPolicy is a helper function to validate additional policy constraints on a per-client basis.
|
||||||
|
func (c *Client) ValidateAuthorizationPolicy(r fosite.Requester) (err error) {
|
||||||
|
form := r.GetRequestForm()
|
||||||
|
|
||||||
|
if c.EnforcePKCE {
|
||||||
|
if form.Get("code_challenge") == "" {
|
||||||
|
return errorsx.WithStack(fosite.ErrInvalidRequest.
|
||||||
|
WithHint("Clients must include a code_challenge when performing the authorize code flow, but it is missing.").
|
||||||
|
WithDebug("The server is configured in a way that enforces PKCE for this client."))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.EnforcePKCEChallengeMethod {
|
||||||
|
if method := form.Get("code_challenge_method"); method != c.PKCEChallengeMethod {
|
||||||
|
return errorsx.WithStack(fosite.ErrInvalidRequest.
|
||||||
|
WithHint(fmt.Sprintf("Client must use code_challenge_method=%s, %s is not allowed.", c.PKCEChallengeMethod, method)).
|
||||||
|
WithDebug(fmt.Sprintf("The server is configured in a way that enforces PKCE %s as challenge method for this client.", c.PKCEChallengeMethod)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient.
|
// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient.
|
||||||
func (c *Client) IsAuthenticationLevelSufficient(level authentication.Level) bool {
|
func (c *Client) IsAuthenticationLevelSufficient(level authentication.Level) bool {
|
||||||
if level == authentication.NotAuthenticated {
|
if level == authentication.NotAuthenticated {
|
||||||
|
@ -48,11 +78,6 @@ func (c *Client) IsAuthenticationLevelSufficient(level authentication.Level) boo
|
||||||
return authorization.IsAuthLevelSufficient(level, c.Policy)
|
return authorization.IsAuthLevelSufficient(level, c.Policy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetID returns the ID.
|
|
||||||
func (c *Client) GetID() string {
|
|
||||||
return c.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSectorIdentifier returns the SectorIdentifier for this client.
|
// GetSectorIdentifier returns the SectorIdentifier for this client.
|
||||||
func (c *Client) GetSectorIdentifier() string {
|
func (c *Client) GetSectorIdentifier() string {
|
||||||
return c.SectorIdentifier
|
return c.SectorIdentifier
|
||||||
|
@ -74,6 +99,11 @@ func (c *Client) GetConsentResponseBody(consent *model.OAuth2ConsentSession) Con
|
||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetID returns the ID.
|
||||||
|
func (c *Client) GetID() string {
|
||||||
|
return c.ID
|
||||||
|
}
|
||||||
|
|
||||||
// GetHashedSecret returns the Secret.
|
// GetHashedSecret returns the Secret.
|
||||||
func (c *Client) GetHashedSecret() []byte {
|
func (c *Client) GetHashedSecret() []byte {
|
||||||
if c.Secret == nil {
|
if c.Secret == nil {
|
||||||
|
|
|
@ -217,6 +217,90 @@ func TestClient_GetResponseTypes(t *testing.T) {
|
||||||
assert.Equal(t, "id_token", responseTypes[1])
|
assert.Equal(t, "id_token", responseTypes[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewClientPKCE(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have schema.OpenIDConnectClientConfiguration
|
||||||
|
expectedEnforcePKCE bool
|
||||||
|
expectedEnforcePKCEChallengeMethod bool
|
||||||
|
expected string
|
||||||
|
req *fosite.Request
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldNotEnforcePKCEAndNotErrorOnNonPKCERequest",
|
||||||
|
schema.OpenIDConnectClientConfiguration{},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
&fosite.Request{},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldEnforcePKCEAndErrorOnNonPKCERequest",
|
||||||
|
schema.OpenIDConnectClientConfiguration{EnforcePKCE: true},
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
&fosite.Request{},
|
||||||
|
"invalid_request",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldEnforcePKCEAndNotErrorOnPKCERequest",
|
||||||
|
schema.OpenIDConnectClientConfiguration{EnforcePKCE: true},
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
&fosite.Request{Form: map[string][]string{"code_challenge": {"abc"}}},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{"ShouldEnforcePKCEFromChallengeMethodAndErrorOnNonPKCERequest",
|
||||||
|
schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"},
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"S256",
|
||||||
|
&fosite.Request{},
|
||||||
|
"invalid_request",
|
||||||
|
},
|
||||||
|
{"ShouldEnforcePKCEFromChallengeMethodAndErrorOnInvalidChallengeMethod",
|
||||||
|
schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"},
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"S256",
|
||||||
|
&fosite.Request{Form: map[string][]string{"code_challenge": {"abc"}}},
|
||||||
|
"invalid_request",
|
||||||
|
},
|
||||||
|
{"ShouldEnforcePKCEFromChallengeMethodAndNotErrorOnValidRequest",
|
||||||
|
schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"},
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"S256",
|
||||||
|
&fosite.Request{Form: map[string][]string{"code_challenge": {"abc"}, "code_challenge_method": {"S256"}}},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
client := NewClient(tc.have)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectedEnforcePKCE, client.EnforcePKCE)
|
||||||
|
assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.EnforcePKCEChallengeMethod)
|
||||||
|
assert.Equal(t, tc.expected, client.PKCEChallengeMethod)
|
||||||
|
|
||||||
|
if tc.req != nil {
|
||||||
|
err := client.ValidateAuthorizationPolicy(tc.req)
|
||||||
|
|
||||||
|
if tc.err != "" {
|
||||||
|
assert.EqualError(t, err, tc.err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestClient_IsPublic(t *testing.T) {
|
func TestClient_IsPublic(t *testing.T) {
|
||||||
c := Client{}
|
c := Client{}
|
||||||
|
|
||||||
|
|
|
@ -107,6 +107,10 @@ type Client struct {
|
||||||
SectorIdentifier string
|
SectorIdentifier string
|
||||||
Public bool
|
Public bool
|
||||||
|
|
||||||
|
EnforcePKCE bool
|
||||||
|
EnforcePKCEChallengeMethod bool
|
||||||
|
PKCEChallengeMethod string
|
||||||
|
|
||||||
Audience []string
|
Audience []string
|
||||||
Scopes []string
|
Scopes []string
|
||||||
RedirectURIs []string
|
RedirectURIs []string
|
||||||
|
|
Loading…
Reference in New Issue