Problem/Motivation

A security scan run by the IT department of one of our clients detected that our openid_connect based SSO didn't use PKCE/the code_verify parameter. It flagged this as a high priority issue so we're trying to add it. We're using this with Azure's Open ID which does recommend using it wherever possible: https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth...

Steps to reproduce/test

  1. Select or configure a site with this module enabled.
  2. Select or create an IdP that supports PKCE. E.g. configure a Google IDP: https://developers.google.com/identity/protocols/oauth2/openid-connect
  3. Navigate to /admin/config/people/openid-connect
  4. Add an OpenID client of the appropriate type, e.g. Google
  5. Populate all relevant details - name, client ID, client secret etc.
  6. Navigate to /admin/config/people/openid-connect/settings
  7. Navigate to /user/login
  8. Open the web browser's developer tools and switch to the network tab
  9. Click the login link for the client you just added
  10. Observe the request to the authorize endpoint - see that it does not include the code_challenge and code_challenge_method parameters (or that it does if you're testing the change instead of reproducing the issue)
  11. If testing this change instead of reproducing the issue complete the login process and make sure it works as expected

Proposed resolution

Add support for the relevant parameters.

Remaining tasks

It might make sense to override some other plugins to support this. E.g. the generic handler could potentially use PKCE based on autodiscovery or a form option.

User interface changes

None yet, but we may want to add an option to the generic plugin as above.

API changes

OpenIDConnectGenericClient and OpenIDConnectClientBase now have a usesPkce() method which by default returns FALSE. If this method is implemented and returns TRUE the plugin will add the code_challenge and code_challenge_method parameters in the initial authorization and the code_verifier parameter when posting to the token endpoint.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Dylan Donkersgoed created an issue. See original summary.

Dylan Donkersgoed’s picture

Status: Active » Needs review

I've opened an MR.

beram made their first commit to this issue’s fork.

beram’s picture

A branch 3266205-pkce-parameter-support-backport-1.2 has been added to backport the support of PKCE in the 8.x-1.2 version.
I left it in draft because I'm not sure that it is the main target here (it has been added because I need this version to support PKCE and maybe others may need it).

angrytoast made their first commit to this issue’s fork.

angrytoast’s picture

Hi there. Apologies if the MR / new branch is unnecessary; I'm not sure what the etiquette is for continuing work on someone else's branch but there have also been some significant changes in the 2.x branch since the original MR from Dylan Donkersgoed. So I elected to make a new branch and MR.

The latest MR is based on the original, targeted at latest 2.x changes. It has some differences from the original:

- Adds configuration settings to the base form so that clients can selectively enable PKCE flow. Adding it to the base client config does mean that updating config and exporting can mean that existing clients can export some unused settings, but it seems better to keep it on the base config rather than replicating the change across clients and their schema.
- Allow selecting between S256 and plain code_challenge transformations per the RFC spec: https://www.rfc-editor.org/rfc/rfc7636#section-4.2
- Some changes in how the code_verifier value is generated--using random_bytes for cryptographically suitable randomness.
- Uses the new session service to store the code_verifier value.

I've tested this against an Okta app with PKCE required and confirmed functioning. Have not tested this with a Google client or other service yet.

jcnventura’s picture

Status: Needs review » Needs work

Usually it would be better to continue from the one existing MR. I've closed the others to make sure that happens now.

But the tests must pass before anything happens here,

angrytoast’s picture

Thanks for the clarification. Regarding tests, I'm not sure how they are run on the d.o infra, but the test fails look to be from missing interface from the externalauth module dependency. Is that module added during the test run so that the interface is available?

1) Drupal\Tests\openid_connect\Unit\Entity\OpenIDConnectClientEntityTest::testGetPluginId
PHPUnit\Framework\MockObject\UnknownTypeException: Class or interface "Drupal\externalauth\AuthmapInterface" does not exist

/var/www/html/vendor/phpunit/phpunit/src/Framework/MockObject/Generator.php:167
/var/www/html/vendor/phpunit/phpunit/src/Framework/MockObject/MockBuilder.php:140
/var/www/html/modules/contrib/openid_connect/tests/src/Unit/Entity/OpenIDConnectClientEntityTest.php:94
/var/www/html/vendor/phpunit/phpunit/src/Framework/TestResult.php:728
/var/www/html/vendor/phpunit/phpunit/src/Framework/TestSuite.php:673
/var/www/html/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:673
/var/www/html/vendor/phpunit/phpunit/src/TextUI/Command.php:144
/var/www/html/vendor/phpunit/phpunit/src/TextUI/Command.php:97

I can run the test locally and it works, but that's because the module is present and installed. It also looks like other MRs have the same fails, e.g. https://www.drupal.org/project/openid_connect/issues/3281137

----

EDIT: Fixed with adding the externalauth dependency to the module composer.json

jcnventura’s picture

Status: Needs work » Needs review

That change is not needed, nor desired. Just want to check if reverting it fixes it, as something is weird.

The externalauth module should be included by default, without having to duplicate the dependency information that is already in the .info.yml.

jcnventura’s picture

OK. The test failed.. That is disappointing. I guess something is cached wrongly somewhere, and the dependencies are not correctly obtained from d.o.

Anyway, needs a few more reviewers to confirm the RTBC status.

angrytoast’s picture

Thanks for the check and update. FWIW, I think the composer.json dependency listing is required. Mainly because the .info.yml only specifies a what (externalauth) but nothing else beyond that, e.g. what version of externalauth.

EDIT:
Per https://www.drupal.org/docs/creating-custom-modules/add-a-composerjson-file

A contributed module that depends on other modules/packages and has automated tests that run on the DrupalCI environment must have a composer.json that expresses the dependencies. Tests of merge requests on the module will fail without a composer.json file, as will tests of patches that change the dependencies.

jcnventura’s picture

Yes, but also: "If a module does not have any dependencies, or the dependencies are solely other Drupal modules, then a composer.json is not required. However, having a composer.json does not have a negative impact either."

This should work. There's something wrong in this issue, however...

See #3292527: Login route to a client that works just fine.

EDIT: I see now.. It only affects tests of merge requests... Well, that's probably the first advantage for using patches that I've seen in some time..

jcnventura’s picture

OK. Tests pass again, since I've committed already the composer.json changes. Learned something with these last comments. Thanks!

angrytoast’s picture

Cool, thanks for making the composer.json changes upstream.

I've merged in origin/2.x changes and the MR should be mergeable again. Not sure what the policy/etiquette is on rebasing and force pushing up, so it's just a merge commit that accounts for the latest 2.x changes.

bircher’s picture

Status: Needs review » Reviewed & tested by the community

This works for us. Though our custom plugin needs to unset the client secret and disable the checkbox on the form because for security reasons PKCE is required and sending the secret is prohibited.

But this is easily achieved with:

  /**
   * {@inheritdoc}
   */
  protected function getTokenRequestOptions(string $authorization_code, string $redirect_uri): array {
    $options = parent::getTokenRequestOptions($authorization_code, $redirect_uri);
    unset($options['form_params']['client_secret']);

    return $options;
  }

So this patch works for us, and since it is opt in, I can RTBC it.

carolpettirossi’s picture

I'm a little confused about how to make the PKCE work with the OAuth2 since client_secret is required. Should I create a custom plugin?

carolpettirossi’s picture

Attaching patch from MR#59 to use with composer.

jcnventura’s picture

Version: 2.x-dev » 3.x-dev
Status: Reviewed & tested by the community » Needs work

I don't really like the explosion of settings in the settings form. Can we move the "On/Off" switch of this into the transformation settings, please? So the transformation options would be "Off / SHA-256 / Plain".

Also this needs a hook_update to add this setting to existing installs.

lorenzs’s picture

In the meantime I can confirm this is working on a D10/PHP8 site with PKCE/SHA256 enabled.
For me the use of the setting in the form is very clear (off, on - then choose transformation) but fine if it would be 1 toggle. That also prevents having 'no' transformation on existing installs when enabling the config only via code - without saving the settings form.

interX made their first commit to this issue’s fork.

interX’s picture

I rerolled the 2.x branch into 3266205-pkce-flow-2022-10--3.x.

I am using this version and everything works as expected here.
The update hook has been added to set the default config of existing plugins.

Note that the new session service that gets injected into the base client will require a change in other contrib modules that provide a client (like openid_connect_windows_aad).

interX changed the visibility of the branch revert-ec71b33d to hidden.

abelass’s picture

I can't apply patch from MR 96 to 3.0.0-alpha2 with composer. Anybody has a working patch for 3. or 2. ?

interX’s picture

You have to use the dev-version to test the patch. composer.json should have an entry like:

"drupal/openid_connect": "^3.x-dev"

lorenzs’s picture

Status: Needs work » Reviewed & tested by the community

Tested.. again.. PKCE is a widely used standard and this issue is now open for more than 2 years keeping us busy with MR's and patches that were tested and reviewed... I do not think anyone is offended by an extra setting in the mentioned config form so please make another alpha release - it is alpha for a reason and setting can easily be skipped if not needed.