OptionProvidor overruled

Created on 30 August 2023, about 1 year ago
Updated 30 October 2023, about 1 year ago

Problem/Motivation

Our services expect that the clientid/clientsecret exists in the authorization header (not in the body).
Therefore i defined an OptionProvidor 'HttpBasicAuthOptionProvider' within the annotation in my Oauth2Client plugin.

* @Oauth2Client(
* id = "my_api",
* name = @Translation("MY API"),
* grant_type = "client_credentials",
* token_uri = "https://my.api.be/oauth/token",
* collaborators = {
* "optionProvider" = "\League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider",
* },
* )

This provider will be overwritten in the plugin Oauth2GrantType/ClientCredentials.php with the ClientCredentialsOptionProvider by this code :

// If the provider was just created, our OptionProvder must be set.
if (!($optionProvider instanceof ClientCredentialsOptionProvider)) {
$provider->setOptionProvider(new ClientCredentialsOptionProvider($clientPlugin));
}

Why is that happening ?

Proposed resolution

When i comment out these previous code lines, then the clientid/clientsecret will be put in de autherization header and i get the token from our services.

Locally i created a patch to comment these lines, but i can imagine that there's an other (beter) solution to solve this problem ?

πŸ’¬ Support request
Status

Closed: works as designed

Version

4.1

Component

Code

Created by

πŸ‡§πŸ‡ͺBelgium pbosmans

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @pbosmans
  • Status changed to Closed: works as designed about 1 year ago
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    The optionProvider on Client Credentials solves this issue: #3157501: Add Scopes to ClientCredentials Grant Type β†’ . Rather than using the upstream default class, you should extend \Drupal\oauth2_client\OAuth2\Client\OptionProvider\ClientCredentialsOptionProvider in your own custom class and I think you can copy right over the code from \League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider::getAccessTokenOptions as it adds what it does onto the options configured by the parent. Your extension will pass the check in the code that you have removed.

  • Status changed to Active about 1 year ago
  • πŸ‡§πŸ‡ͺBelgium pbosmans

    I've created our own custom class as you suggested, but how can i add the scopes to the request.
    My optionprovider is created in the Oauth2ClientPluginBase.php at line 211 => $collaboratorObjects[$type] = new $collaborator();
    I can't use tesame method like the ClientCredentials option providor => $provider->setOptionProvider(new ClientCredentialsOptionProvider($clientPlugin));
    It has a parameter and the custom optionsprovider not.

  • Status changed to Postponed: needs info about 1 year ago
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    I wouldn't set this option provider via the annotation method (which is used at Oauth2ClientPluginBase:211 as you note). I would leave that off and in your plugin, override and extend Oauth2ClientPluginBase::getProvider.

    Assuming:

    class SomeServiceAuthOptionProvider extends ClientCredentialsOptionProvider {
    
      public function getAccessTokenOptions($method, array $params) {
        // copied from \League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider
        if (empty($params['client_id']) || empty($params['client_secret'])) {
              throw new InvalidArgumentException('clientId and clientSecret are required for http basic auth');
         }
    
         $encodedCredentials = base64_encode(sprintf('%s:%s', $params['client_id'], $params['client_secret']));
          unset($params['client_id'], $params['client_secret']);
    
          $options = parent::getAccessTokenOptions($method, $params);
          $options['headers']['Authorization'] = 'Basic ' . $encodedCredentials;
    
          return $options;
      }
    
    }
    

    In your plugin, the override would be

      public function getProvider(): GenericProvider {
        $provider = parent::getProvider();
        $client = $this->getClient();
    
        $provider->setOptionProvider(new SomeServiceAuthOptionProvider($clientPlugin));
        return $provider;
      }
    

    Then the provider will be set when the code flow gets to ClientCredentials::getAccessToken and it will be an instance of ClientCredentialsOptionProvider

  • Status changed to Closed: works as designed about 1 year ago
  • πŸ‡§πŸ‡ͺBelgium pbosmans

    Thank you very much for your response, it solved the issue.
    Here are my changes for others who has tesame issues
    My custom optionprovider :

    class UcllHttpBasicAuthOptionProvider extends ClientCredentialsOptionProvider {
    
      /**
       * A string of scopes imploded from the Oauth2ClientPlugin.
       */
      private string $scopeOption;
    
      public function __construct(Oauth2ClientPluginInterface $clientPlugin) {
        $scopes = $clientPlugin->getScopes();
        if (!empty($scopes)) {
          $this->scopeOption = implode($clientPlugin->getScopeSeparator(), $scopes);
        }
      }
      public function getAccessTokenOptions($method, array $params) {
        if (empty($params['client_id']) || empty($params['client_secret'])) {
          throw new InvalidArgumentException('clientId and clientSecret are required for http basic auth');
        }
    
        $encodedCredentials = base64_encode(sprintf('%s:%s', $params['client_id'], $params['client_secret']));
        unset($params['client_id'], $params['client_secret']);
    
        if (!empty($this->scopeOption)) {
          $params['scope'] = $this->scopeOption;
        }
    
        $options = parent::getAccessTokenOptions($method, $params);
        $options['headers']['Authorization'] = 'Basic ' . $encodedCredentials;
    
        return $options;
      }
    }
    

    My oauth2client plugin :

    /**
     * OAuth2 Client to authenticate with our services
     *
     * The client_id and client_secret is set through /admin/config/system/oauth2-client
     *
     * @Oauth2Client(
     *   id = "<your-client-id>",
     *   name = @Translation("<YOUR CLIENT NAME>"),
     *   grant_type = "client_credentials",
     *   token_uri = "https://<your-domain>/oauth2/token",
     *   authorization_uri = "",
     *   resource_owner_uri = "",
     *   scopes = { "<your-scopes>" },
     * )
     */
    class UcllApi extends Oauth2ClientPluginBase {
    
      use StateTokenStorage;
    
      /**
       * {@inheritdoc}
       */
      public function getProvider(): GenericProvider {
        $provider = parent::getProvider();
        $provider->setOptionProvider(new UcllHttpBasicAuthOptionProvider($this));
        return $provider;
      }
    
      /**
       * {@inheritdoc}
       */
      public function getRedirectUri(): string {
        return "";
      }
    }
    
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    Happy to help and glad it worked out!

  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
Production build 0.71.5 2024