PKCE implementation (#1784)

* Basic implementation of PKCE

Signed-off-by: Tadeusz Magura-Witkowski <tadeuszmw@gmail.com>

* @mfmarche on 24 Feb: when code_verifier is set, don't check client_secret

In PKCE flow, no client_secret is used, so the check for a valid client_secret
would always fail.

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* @deric on 16 Jun: return invalid_grant when wrong code_verifier

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* Enforce PKCE flow on /token when PKCE flow was started on /auth
Also dissallow PKCE on /token, when PKCE flow was not started on /auth

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* fixed error messages when mixed PKCE/no PKCE flow.

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* server_test.go: Added PKCE error cases on /token endpoint

* Added test for invalid_grant, when wrong code_verifier is sent
* Added test for mixed PKCE / no PKCE auth flows.

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* cleanup: extracted method checkErrorResponse and type TestDefinition

* fixed connector being overwritten

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* /token endpoint: skip client_secret verification only for grand type authorization_code with PKCE extension

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* Allow "Authorization" header in CORS handlers

* Adds "Authorization" to the default CORS headers{"Accept", "Accept-Language", "Content-Language", "Origin"}

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* Add "code_challenge_methods_supported" to discovery endpoint

discovery endpoint /dex/.well-known/openid-configuration
now has the following entry:

"code_challenge_methods_supported": [
  "S256",
  "plain"
]

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* Updated tests (mixed-up comments), added a PKCE test

* @asoorm added test that checks if downgrade to "plain" on /token endpoint

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* remove redefinition of providedCodeVerifier, fixed spelling (#6)

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
Signed-off-by: Bernd Eckstein <HEllRZA@users.noreply.github.com>

* Rename struct CodeChallenge to PKCE

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* PKCE: Check clientSecret when available

In authorization_code flow with PKCE, allow empty client_secret on /auth and /token endpoints. But check the client_secret when it is given.

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* Enable PKCE with public: true

dex configuration public on staticClients now enables the following behavior in PKCE:
- Public: false, PKCE will always check client_secret. This means PKCE in it's natural form is disabled.
- Public: true, PKCE is enabled. It will only check client_secret if the client has sent one. But it allows the code flow if the client didn't sent one.

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* Redirect error on unsupported code_challenge_method

- Check for unsupported code_challenge_method after redirect uri is validated, and use newErr() to return the error.
- Add PKCE tests to oauth2_test.go

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* Reverted go.mod and go.sum to the state of master

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* Don't omit client secret check for PKCE

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* Allow public clients (e.g. with PKCE) to have redirect URIs configured

Signed-off-by: Martin Heide <martin.heide@faro.com>

* Remove "Authorization" as Accepted Headers on CORS, small fixes

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* Revert "Allow public clients (e.g. with PKCE) to have redirect URIs configured"

This reverts commit b6e297b78537dc44cd3e1374f0b4d34bf89404ac.

Signed-off-by: Martin Heide <martin.heide@faro.com>

* PKCE on client_secret client error message

* When connecting to the token endpoint with PKCE without client_secret, but the client is configured with a client_secret, generate a special error message.

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* Output info message when PKCE without client_secret used on confidential client

* removes the special error message

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

* General missing/invalid client_secret message on token endpoint

Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>

Co-authored-by: Tadeusz Magura-Witkowski <tadeuszmw@gmail.com>
Co-authored-by: Martin Heide <martin.heide@faro.com>
Co-authored-by: M. Heide <66078329+heidemn-faro@users.noreply.github.com>
This commit is contained in:
Bernd Eckstein
2020-10-26 11:33:40 +01:00
committed by GitHub
parent 2a282860fa
commit b5519695a6
10 changed files with 439 additions and 53 deletions

View File

@@ -130,10 +130,11 @@ func (c *conn) CreateAuthRequest(a storage.AuthRequest) error {
claims_user_id, claims_username, claims_preferred_username,
claims_email, claims_email_verified, claims_groups,
connector_id, connector_data,
expiry
expiry,
code_challenge, code_challenge_method
)
values (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20
);
`,
a.ID, a.ClientID, encoder(a.ResponseTypes), encoder(a.Scopes), a.RedirectURI, a.Nonce, a.State,
@@ -142,6 +143,7 @@ func (c *conn) CreateAuthRequest(a storage.AuthRequest) error {
a.Claims.Email, a.Claims.EmailVerified, encoder(a.Claims.Groups),
a.ConnectorID, a.ConnectorData,
a.Expiry,
a.PKCE.CodeChallenge, a.PKCE.CodeChallengeMethod,
)
if err != nil {
if c.alreadyExistsCheck(err) {
@@ -172,8 +174,9 @@ func (c *conn) UpdateAuthRequest(id string, updater func(a storage.AuthRequest)
claims_email = $12, claims_email_verified = $13,
claims_groups = $14,
connector_id = $15, connector_data = $16,
expiry = $17
where id = $18;
expiry = $17,
code_challenge = $18, code_challenge_method = $19
where id = $20;
`,
a.ClientID, encoder(a.ResponseTypes), encoder(a.Scopes), a.RedirectURI, a.Nonce, a.State,
a.ForceApprovalPrompt, a.LoggedIn,
@@ -181,7 +184,9 @@ func (c *conn) UpdateAuthRequest(id string, updater func(a storage.AuthRequest)
a.Claims.Email, a.Claims.EmailVerified,
encoder(a.Claims.Groups),
a.ConnectorID, a.ConnectorData,
a.Expiry, r.ID,
a.Expiry,
a.PKCE.CodeChallenge, a.PKCE.CodeChallengeMethod,
r.ID,
)
if err != nil {
return fmt.Errorf("update auth request: %v", err)
@@ -201,7 +206,8 @@ func getAuthRequest(q querier, id string) (a storage.AuthRequest, err error) {
force_approval_prompt, logged_in,
claims_user_id, claims_username, claims_preferred_username,
claims_email, claims_email_verified, claims_groups,
connector_id, connector_data, expiry
connector_id, connector_data, expiry,
code_challenge, code_challenge_method
from auth_request where id = $1;
`, id).Scan(
&a.ID, &a.ClientID, decoder(&a.ResponseTypes), decoder(&a.Scopes), &a.RedirectURI, &a.Nonce, &a.State,
@@ -210,6 +216,7 @@ func getAuthRequest(q querier, id string) (a storage.AuthRequest, err error) {
&a.Claims.Email, &a.Claims.EmailVerified,
decoder(&a.Claims.Groups),
&a.ConnectorID, &a.ConnectorData, &a.Expiry,
&a.PKCE.CodeChallenge, &a.PKCE.CodeChallengeMethod,
)
if err != nil {
if err == sql.ErrNoRows {
@@ -227,13 +234,15 @@ func (c *conn) CreateAuthCode(a storage.AuthCode) error {
claims_user_id, claims_username, claims_preferred_username,
claims_email, claims_email_verified, claims_groups,
connector_id, connector_data,
expiry
expiry,
code_challenge, code_challenge_method
)
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14);
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16);
`,
a.ID, a.ClientID, encoder(a.Scopes), a.Nonce, a.RedirectURI, a.Claims.UserID,
a.Claims.Username, a.Claims.PreferredUsername, a.Claims.Email, a.Claims.EmailVerified,
encoder(a.Claims.Groups), a.ConnectorID, a.ConnectorData, a.Expiry,
a.PKCE.CodeChallenge, a.PKCE.CodeChallengeMethod,
)
if err != nil {
@@ -252,12 +261,14 @@ func (c *conn) GetAuthCode(id string) (a storage.AuthCode, err error) {
claims_user_id, claims_username, claims_preferred_username,
claims_email, claims_email_verified, claims_groups,
connector_id, connector_data,
expiry
expiry,
code_challenge, code_challenge_method
from auth_code where id = $1;
`, id).Scan(
&a.ID, &a.ClientID, decoder(&a.Scopes), &a.Nonce, &a.RedirectURI, &a.Claims.UserID,
&a.Claims.Username, &a.Claims.PreferredUsername, &a.Claims.Email, &a.Claims.EmailVerified,
decoder(&a.Claims.Groups), &a.ConnectorID, &a.ConnectorData, &a.Expiry,
&a.PKCE.CodeChallenge, &a.PKCE.CodeChallengeMethod,
)
if err != nil {
if err == sql.ErrNoRows {
@@ -317,7 +328,7 @@ func (c *conn) UpdateRefreshToken(id string, updater func(old storage.RefreshTok
claims_email_verified = $8,
claims_groups = $9,
connector_id = $10,
connector_data = $11,
connector_data = $11,
token = $12,
created_at = $13,
last_used = $14