How to get it working???

Created on 18 March 2025, about 1 month ago

I am trying to connect to USPS from another module. I have credentials and I have setup the Oauth2 ugin directory there and modified the files with the namespace, data and URLs

I looked at Oauth2ClientForm.php and while I changed my modules form for the needed credentials, and it sends them, there are other software issues not related to simply getting a connection.

I noticed that the config page is supposed to have a test client "button" but there is none.

I also notice the two lines to get a token using the service, but I do not know where this goes in the code. Putting it in generates an error "function expected" or that the rest of the working code is now gone.

I can try copying the client_id and client_secret code to the file which has my form language code but like in issue 3472221 I am guessing.

Some support help on configuring would be nice???

🐛 Bug report
Status

Active

Version

4.1

Component

Code

Created by

🇺🇸United States bobburns

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

Comments & Activities

  • Issue created by @bobburns
  • 🇺🇸United States fathershawn New York

    Can you be more specific about what credential data you need beyond client id and secret?

  • 🇺🇸United States bobburns

    A "CRID" - Customer Registration Identification and a "MID" - Mailer Identification. The Postal service shows the example

    Prepare your credentials for use in the following examples:

    CLIENT_ID=XXXX
    CLIENT_SECRET=XXXX
    CUSTOMER_REGISTRATION_ID=XXXX
    MAILER_ID=XXXX

    Example OAuth Client Credentials Token request:

    curl -X 'POST' 'https://apis.usps.com/oauth2/v3/token' \
    --header 'Content-Type: application/json' \
    --data '{
    "client_id": "{{CLIENT_ID}}",
    "client_secret": "{{CLIENT_SECRET}}",
    "grant_type": "client_credentials"
    }'

    at https://github.com/USPS/api-examples using CURL.

    However I do not get the form Oauth2ClientForm.php anywhere

    In the above example I simply used the small case code call and Oauth2_client does not report any installed clients

  • 🇺🇸United States fathershawn New York

    You will see the form when you configure the client. I know the documentation is minimal but have you followed https://www.drupal.org/docs/contributed-modules/oauth2-client/oauth2-cli... ?

  • 🇺🇸United States bobburns

    Yes I did that. I copied all four examples, changed the namespace to the module name, and put it in Plugin/Oauth2Client under the "src" directory of my module and adjusted the files removing "Example" from everywhere it was found and replaced theurls with the correct ones for USPS and removed the " * " from the " @0auth2Client( " code

    There is nothing in admin/config/Oauth2-client and says no clients are configured yet

  • 🇺🇸United States fathershawn New York

    If you don't see your plugin listed at /admin/config/system/oauth2-client then your plugin is not being discovered.

    You should see it listed like this:

    And when you edit it you will see a credential section. With Key module installed you will see a selector for provider and when Key is selected a select to choose your key.

    Double check the placement of your plugin class in your custom module. It should be in directory src/Plugin/Oauth2Client

    Are you using Attribute or Annotation for metadata?

  • 🇺🇸United States bobburns

    Thank you for that. I use WinSCP, so I copied the entire "Examples" directory to my module and changed ONLY the namespace. Still nothing until the caches were cleared. Up came the files and the form was accessible; however, if the file is directly modified to remove the "*" - then it does not like the equals sign and throws

    ParseError: syntax error, unexpected token "=", expecting ")" in Composer\Autoload\{closure}() (line 18 of modules/commerce_usps/src/Plugin/Oauth2Client/AuthCodeExample.php). and for any line with an " = " sign

    Also the clients are impossible to delete, are they entities which must be deleted with entity delete

    Thus there is no url to request a token from

    I renamed one file to remove the "example" from it php name by duplicating it - and it worke, however still it too is impossible to delete.

    Even if I delete the entire Oauth2client directory from the plugin directory still it complains lookin for the old files.

    Putting them back will let it work again

    But this is still a "BUG" because of the " = " sign issue and th impossibility to delete clients

    Where does and how does the

    $access_token = Drupal::service('oauth2_client.service')->getAccessToken($client_id);
    $token = $access_token->getToken();

    go in the code??

    It should be noted the "Instagram" connection syntax appears to work with the " : " instead of the " = " sign as at least it does not throw a syntax error, but the file still seems to locked to " oauth.mocklab.io " as the authorization url so it still does not work even I clear the cache and / or rebuild the cache

    Then it throws

    Drupal\Component\Plugin\Exception\PluginException: Plugin (authcode_example) instance class "Drupal\xxxxxxxxxxx\Plugin\Oauth2Client\AuthCode" does not exist. in Drupal\Component\Plugin\Factory\DefaultFactory::getPluginClass() (line 97 of core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php).

    finally after that it throws

    ParseError: syntax error, unexpected token "class" in Composer\Autoload\{closure}() (line 38 of modules/xxxxxxxxxxxxx/src/Plugin/Oauth2Client/AuthCodeExample.php).

    and in the end after editing anything the plugin seems to no longer exist

    Drupal\Component\Plugin\Exception\PluginNotFoundException: The "authcode_example" plugin does not exist. Valid plugin IDs for Drupal\oauth2_client\PluginManager\Oauth2ClientPluginManager are: resource_owner_example, authcode_access_example, authcode_redirect_example in Drupal\Core\Plugin\DefaultPluginManager->doGetDefinition() (line 53 of core/lib/Drupal/Component/Plugin/Discovery/DiscoveryTrait.php).

  • 🇺🇸United States fathershawn New York

    I recommend that you start over. The examples are intended to document how you would create a plugin class for your use case. None of them will work as is for your use case as your grant type is not used in any of the examples.

    I recommend that you remove all of your copies and start fresh. If you need step by step guidance in building a plugin class, we can do that here, just post your code after each step below. In general you need to:

    1. Create your own plugin class that extends \Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginBase
    2. Decide on the appropriate token storage for your use case and use the provided trait for that storage
    3. Decide on Attribute or Annotation for your plugin definition method.
    4. Add the proper data using Attribute/Annotation to setup the plugin for your use case. Possible values are in \Drupal\oauth2_client\Annotation\Oauth2Client or \Drupal\oauth2_client\Attribute\Oauth2Client.
  • 🇺🇸United States bobburns

    OK - that is where I started before this report, so I put the files back with edited urls - BEFORE - flushing the caches. The pic below shows the result - it worked and I got a "GET" not allowed error on my credentials. See second attached screenshot

    The documentation shows the " * " comment frame removed for the "Instagram" access example - while it ony works leaving it in place. I renamed the files to single word titles as classes

    You still did not say where does and how does the

    $access_token = Drupal::service('oauth2_client.service')->getAccessToken($client_id);
    $token = $access_token->getToken();

    go in the code??

    The documentation should be adjusted to show the proper order to edit the files before flushing the caches to seek discovery

    I have edited the client form to include the CRID and MID, but currently it does not save. I am going over the code more closely. Those missing credentials may cause errors also

    So does editing the file after discovery do NOTHING?

    my "Code.php"file is below

    <?php

    declare(strict_types=1);

    namespace Drupal\commerce_usps\Plugin\Oauth2Client;

    use Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginBase;
    use Drupal\oauth2_client\Plugin\Oauth2Client\StateTokenStorage;

    /**
    * Auth code
    *
    * @Oauth2Client(
    * id = "code",
    * name = @Translation("Code grant"),
    * grant_type = "client_credentials",
    * authorization_uri = "https://apis.usps.com/oauth2/v3/token",
    * token_uri = "https://apis.usps.com/oauth2/v3/token",
    * success_message = TRUE
    * )
    */

    class Code extends Oauth2ClientPluginBase {

    /*
    * This example assumes that the Drupal site is using a shared resource
    * from a third-party service that provides a service to all uses of the site.
    *
    * Storing a single AccessToken in state for the plugin shares access to the
    * external resource for ALL users of this plugin.
    */

    use StateTokenStorage;

    }

  • 🇺🇸United States fathershawn New York

    So does editing the file after discovery do NOTHING?

    I can see that you are frustrated, and I am trying to help, so please try to be patient and don't shout. There are a lot of questions in your last comment. It will be much easier in this format if we go step by step, but I'll try to answer all your questions and then go back to the step 1 I requested in #9.

    The documentation shows the " * " comment frame removed for the "Instagram" access example - while it ony works leaving it in place. I renamed the files to single word titles as classes

    I see the difference now between attribute and annotation. The examples are attribute, and the Instagram example is annotation.

    The code examples in README.md are meant to illustrate both the new Attribute based discovery/metadata and the retiring Annotation based approach. A made up example for Instagram is given in both syntaxes. Both are supported for now and Attribute is the long term approach. Attributes use a PHP8 language feature and Annotations use comments.

    The examples sub-module still uses the older Annotation syntax.

    You still did not say where does and how does the

    $access_token = Drupal::service('oauth2_client.service')->getAccessToken($client_id);
    $token = $access_token->getToken();

    go in the code??

    The purpose of this module is to make it easy for you to get the token value in your own code, without needing to build an oauth client. When you make a request to an Oauth2 protected api, such as the USPS api that you referer to, you need this token in your request header to authorize the request. Your api give a curl based example;

    curl	-X 'GET' 'https://apis.usps.com/addresses/v3/address?streetAddress=3120%20M%20St&secondaryAddress=NW&city=Washington&state=DC&ZIPCode=20027&ZIPPlus4=3704' \
    	--header 'accept: application/json' \
    	--header 'authorization: Bearer $TOKEN' \
    

    PHP commonly uses Guzzle for such requests and Drupal provides a factory for Guzzle clients, \Drupal\Core\Http\ClientFactory which has a means for setting these headers.

    I have edited the client form to include the CRID and MID.

    I don't recommend this as I have not had time to implement plugin specific forms. I don't see anywhere in the api documents that you linked that these values are needed to get a token, which is our goal here. There's a way to include them in a custom Key file but let's set that aside for now.

    Now your custom plugin looks like it's account for all 4 of my steps. This looks right to me, but you should remove the blank line between the annotation and class declaration.

    declare(strict_types=1);
    
    namespace Drupal\xxxxxxxxxxxxx\Plugin\Oauth2Client;
    
    use Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginBase;
    use Drupal\oauth2_client\Plugin\Oauth2Client\StateTokenStorage;
    
    /**
    * Auth code
    *
    * @Oauth2Client(
    * id = "code",
    * name = @Translation("Code grant"),
    * grant_type = "client_credentials",
    * authorization_uri = "https://apis.usps.com/oauth2/v3/token",
    * token_uri = "https://apis.usps.com/oauth2/v3/token",
    * success_message = TRUE
    * )
    */
    class Code extends Oauth2ClientPluginBase {
      use StateTokenStorage;
    }
    

    I don't recommend storing your secrets in config, but for testing it's okay. If you remove the changes you made to my form, enter your client id and secret and test your plugin. If it works, you will get a message

    /**
       * Stores access tokens obtained by the client.
       *
       * @param \League\OAuth2\Client\Token\AccessTokenInterface $accessToken
       *   The token to store.
       */
      public function storeAccessToken(AccessTokenInterface $accessToken): void {
        $this->state->set('oauth2_client_access_token-' . $this->getId(), $accessToken);
        if ($this->displaySuccessMessage()) {
          $this->messenger->addStatus(
            $this->t('OAuth token stored.')
          );
        }
      }
    
  • 🇺🇸United States bobburns

    Thank you, but not frustrated. It simply appears that edits made to the file after discovery in fact do nothing. USPS has a funky Oauth2 implementation. I don't know where it uses it but the CRID and MID is needed before access is granted to the actual API. In the first client credentials the API example says

    Prepare your credentials for use in the following examples:

    CLIENT_ID=XXXX
    CLIENT_SECRET=XXXX
    CUSTOMER_REGISTRATION_ID=XXXX
    MAILER_ID=XXXX

    It is above in number 4 where I answer your question.

    I tried editing the file after discovery to change the URLs called to but it kept going to mocklab.io

    So if I change the file to remove the space line, I suspect it will do nothing unless I uninstall the Oauth2Client module to destroy the discovered plug-ins and re install and clear caches to cause discovery again

    In the examples page from USPS the access grant shows the payload that comes back includes CRID and application name.

    It does not show here but I had to add the application by name to my USPS developer account which is then associated with my client_ID, secret, CRID and MID sent of which the MID is a number associated with a mailing location. There can be many MID for each location but only one CRID. You can only get a developer account with a full business account linked to a bank account like for printing labels.

    So I don't know, but it is possible no token would be sent without the CRID and MID.

    The second pic I attached is a response into the browser from USPS where I used an "authorization code" grant type and it threw a no "GET" allowed error

    So it worked to contact USPS for a Token which failed.

    I changed it to "client credentials" in the file and there was no error thrown to the browser, but no message in the log either.

    I duplicated the file, changed the name and class and re-discovered it and disabled the other and enabled the new one again the same, no error, but no message in the logs.

    USPS will return a json payload even if it fails, so that my question on a catch on success or fail.

    Renaming the client form to the original means the only way I would know is uninstall the Oauth2_client module and start with a fresh discovery.

    That is why I asked how and if the plugins can be separately deleted.

  • 🇺🇸United States fathershawn New York

    if I cannot get and store a token for access , that is moot.

    These plugins will handle requesting and storing your token. Since you have success_message set to true in your definition, the code I posted at the end of #11 will tell you that a token was stored.

    Plugin definitions are cached in Drupal, but a cache rebuild will refresh the definition. To verify that this is working, change the name property from "Code grant" to something else, maybe "USPS", and rebuild your cache. You should see the name change on the configuration page.

    I think the source of your failure is missing scope in your client definition. Looking at the USPS API documentation, scope is required and it differs between APIs. For example, the address api (https://developers.usps.com/addressesv3#tag/Resources) within the Authorize details has Required scopes: addresses It looks to me like you will want a Drupal plugin for each of these API endpoints in which you set the scope property in your plugin to the required value. You could try a list of scopes, the most common separator is comma but the Oauth2 spec is space delimited and that API is already being obstinate so I'd at least start with just one scope and use it for one endpoint.

  • 🇺🇸United States bobburns

    Are you saying to add the code from #11 to the plugin or elsewhere or is it already existing code elsewhere in the module ???

    To the plugin throws

    ParseError: syntax error, unexpected token "public", expecting end of file in Composer\Autoload\{closure}() (line 41 of /var/www/public_html/modules/xxxxxxxxxxxxxxx/src/Plugin/Oauth2Client/Auth2Code.php).
    Severity Error

    Removing "public" from the function allows it to run, but no message appears or is logged

    As to the "scope" suggestion, it is my understanding until an initial token is obtained there is no API access and then the scope access request issues are then needed. The initial token is critical to moving forward at all. You cannot jump into scope issues at all.

    Mine is waiting as

    <?php

    declare(strict_types=1);

    namespace Drupal\xxxxxxxxxxxxxx\Plugin\Oauth2Client;

    use Drupal\Core\Access\AccessResult;
    use Drupal\Core\Access\AccessResultInterface;
    use Drupal\Core\Session\AccountInterface;
    use Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginAccessInterface;
    use Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginBase;
    use Drupal\oauth2_client\Plugin\Oauth2Client\TempStoreTokenStorage;

    /**
    * Access code
    *
    * @Oauth2Client(
    * id = "authaccess",
    * name = @Translation("Auth Access grant"),
    * grant_type = "authorization_code",
    * code = "$token",
    * redirect_uri = "https://xxxxxxxxxxxxx.com/authorize",
    * scope = "prices labels tracking",
    * state = "nonce=abscdefg#",
    * success_message = TRUE
    * )
    */
    class AuthAccess extends Oauth2ClientPluginBase implements Oauth2ClientPluginAccessInterface {

    /*
    * This example assumes that a user is authenticating against a third-party
    * service to retrieve a token that Drupal can use to access resources on
    * that user's behalf.
    */
    use TempStoreTokenStorage;

    /**
    * {@inheritdoc}
    */
    public function codeRouteAccess(AccountInterface $account): AccessResultInterface {
    return AccessResult::allowedIfHasPermissions($account, ['access content']);
    }

    }

    The USPS example was originally "code": "{{CODE}}", that I guessed "CODE" was the token I changed to code = "$token",

    Of course without a $token there is no variable $token

    Chamging the file and rebuilding the cache does no show up changes in the config page for me - only a fresh file and flushing cahes to rediscover works.

    Still is there anyway to delete those plugins separately??

    Looks like I am going to need to try the actual USPS CURL examples through my other file which will catch the response errors to the log

  • 🇺🇸United States fathershawn New York

    Are you saying to add the code from #11 to the plugin or elsewhere or is it already existing code elsewhere in the module ???

    It's in the trait that you have included in the plugin already.

    As to the "scope" suggestion, it is my understanding until an initial token is obtained there is no API access and then the scope access request issues are then needed. The initial token is critical to moving forward at all. You cannot jump into scope issues at all.

    This is not in keeping with the spec: https://datatracker.ietf.org/doc/html/rfc6749#section-3.3 explained in more plain language at https://oauth.net/2/scope/. The scope is encoded in your token.

    You've added an unsupported property into your plugin definition, which is probably benign but I'm not sure:

     * state = "nonce=abscdefg#",
    

    This module takes care of the state management.

    You also have removed the two essential urls from the plugin definition for the authorization code flow. This module includes a default redirect url but you need these two as the module has no idea how to communicate with USPS.

     *   authorization_uri = "https://apis.usps.com//oauth2/v3/authorize",
     *   token_uri = "https://apis.usps.com/oauth2/v3/token",
    
    Changing the file and rebuilding the cache does not show up changes in the config page for me - only a fresh file and flushing caches to rediscover works.

    That's my mistake, forgetting a recent architecture improvement. I'll add an improvement ticket that makes it easier to clear. What's going on there is the UI is showing a set of configuration entities, which match 1:1 with the plugins, and are created when plugins are discovered. But plugin definitions are 100% cleared when the cache is rebuilt and any new plugin ids get paired with a config entity in \Drupal\oauth2_client\Plugin\Discovery\Oauth2ClientDiscoveryDecorator::getDefinitions

  • 🇺🇸United States bobburns

    I am just following the USPS examples of what they say is required. I have not removed urls - I copied the example from USPS. They know how they have setup the authorization flow, and I am only guessing the "code" I mentioned is the prior token received. The specification you linked tends to indicate the authorization server can be set up that way. So without knowing what "CODE" they say is required the access token it would never be granted, and only the client credentials authorization code file might work. After creating a new file and editing it to add the authorize url and then flushing the cache for a rediscovery, still nothing - no error and no sucess message either

    USPS is not likely to support this oauth2client module use, so without something caught in the communication to analyze this will never work

    I even buit a plugin to throw the "GET" not allowed error into the broweser and it does so on /authorize so communication to USPS is still going on correctly

    Something has to going on with JSON

  • 🇺🇸United States fathershawn New York

    The plan was to use the Oauth2Client module for token management purposes for access only, Otherwise yes I would need far too many plugins. My other file is set up for CURL calls that would handle the API calls. I would only need the initial client credentials for authcode file, and the access file, and a refresh file I have not built yet

    That's a good plan, and this module is not only for getting the initial token but for managing the refresh token. It allows a developer to just use Oauth and not mess with the details. With this module you don't have to manage the details of an auth code flow, neither the code nor the state parameter, nor storing the refresh token, nor using the refresh token to get a fresh token. All of that is what this module is for. It's not clear from this thread why the USPS service isn't working for you. I don't have access to it so can't debug it.

    This module is built to integrate Drupal with the excellent Oauth2 Client PHP library, and both it and the upstream library conform to the Oauth2 standards.

    It seems like you are facing two challenges. One is implementing Oauth in your Drupal code and the other is connecting with USPS. My only remaining suggestion is to simplify the problem and just work at implementing Oauth in your code. The folks at Okta provide an Oauth playground. They don't offer client credentials but they do offer Auth code workflow. You can register an account and get oauth working in your code. Then you can repoint your working code at USPS, and if it doesn't work then something is off and you should reach out to them.

  • 🇺🇸United States bobburns

    This morning when backing out of the response from USPS thrown to the browser for the first time a Drupak message appeared

    Updated oauth2 client Auth4Code token grant.

    This is from the plugin I built to fail thrown in response to the url window of the browser

    https://apis.usps.com//oauth2/v3/authorize?state=d42e366ad0b866095cee735...

    I have not been able to reproduce that message

    I believe I can hack together the initial token crendential USPS example curl call using my config menu and a temporary postfield of client credentials grant type which should throw a response to the log

    Then I will try the Simple oauth module - then I will contact USPS with questions

    Is there any way to change code in this module to temporarily throw all responses to the browser???

  • 🇺🇸United States fathershawn New York

    Yes, you can install the Devel Kint module and use the dpr() function to print any variable to data to the browser.
    See https://www.drupal.org/docs/extending-drupal/contributed-modules/contributed-module-documentation/devel/introduction

    I still recommend you simplify your problem and get an auth code flow working against a reliable testing service and then pivot to USPS.

  • 🇺🇸United States bobburns

    Thank you. It is working using Postman. It does not work sent as a header, only if credentials are sent in the body.

    From what I can tell of the code, the request has no provision in Oauth2Client to be sent in the body

    Also the "client credentials" is a POST request not a GET.

  • 🇺🇸United States fathershawn New York

    The token request is normally sent in the body here.

    Look at \Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginBase::getProvider.

    This method instantiates \League\OAuth2\Client\Provider\GenericProvider

    Look at \League\OAuth2\Client\Provider\GenericProvider::getAccessTokenMethod which if there is no override value created above calls \League\OAuth2\Client\Provider\AbstractProvider::getAccessTokenMethod which returns POST.

    The initial auth code request is a GET to a url on the remote service where the user logs in, authorizes the connection. This generates a code which is sent in a query parameter to the original site (Drupal) which then makes a POST to a token url with that code to get the token.

    This module would allow you to completely customize all the available options in GenericProvider or AbstractProvider by overriding ::getProvider in your plugin and changing how the provider is instantiated.

  • 🇺🇸United States bobburns

    OK, thanks I will see what I can do in the client form with a for example

    if ($grantType == 'client_credentials') {
    $form['oauth2_client']['data in body'] = [
    '#type' => 'checkbox',
    '#title' => $this->t('Enabled'),
    '#default_value' => $this->entity->
    (),
    ];

    As needed et cetera and it will be a stretch to my skills to cobble together but there is other code helpful on the internet on the issue as it is a common problem one

  • 🇺🇸United States fathershawn New York
  • 🇺🇸United States bobburns

    OK, tired of this or is there no solution??

    I came across a similar issue with client_credentials - but that poster wanting to send for authorization in the headers and not in the body at https://www.drupal.org/project/oauth2_client/issues/3384244 💬 OptionProvidor overruled Closed: works as designed . You wrote a solution there.

    I am studying how I might modify that. It looks like the

    * optionProvider = "\League\OAuth2\Client\OptionProvider\PostAuthOptionProvider",

    might be useful, unless I am reading it incorrectly

    I then discovered the client id and secret could be called in the plugin itself - like somethng as for a plugin as

    /**
    * Auth code
    *
    * @Oauth2Client(
    * id = "auth7code",
    * name = @Translation("Auth7Code token grant"),
    * grant_type = "client_credentials",
    * client_id = "$credentials['client_id']",
    * client_secret = "$credentials['client_secret']",
    * authorization_uri = "https://apis.usps.com//oauth2/v3/token",
    * token_uri = "https://apis.usps.com/oauth2/v3/token",
    * optionProvider = "\League\OAuth2\Client\OptionProvider\PostAuthOptionProvider",
    * success_message = TRUE
    * )
    */

    In the code you wrote is these two lines

    $options = parent::getAccessTokenOptions($method, $params);
    $options['headers']['Authorization'] = 'Basic ' . $encodedCredentials;

    I know $method is the variable for POST or GET, but I cannot find where it is set

    and I know this second $options should in part be

    $options['body']['Change to??'] = 'Change to ??' . $encodedCredentials;

    If you do not want to assist further - I get it - but this is the way a POST should be sent for security as other locations on the web have said

  • 🇺🇸United States fathershawn New York

    I'm happy to keep chatting. This felt like a conclusion to me:

    OK, thanks I will see what I can do in the client form with a for example

    if ($grantType == 'client_credentials') {
    $form['oauth2_client']['data in body'] = [
    '#type' => 'checkbox',
    '#title' => $this->t('Enabled'),
    '#default_value' => $this->entity->point to the code needed(),
    ];

    As needed et cetera and it will be a stretch to my skills to cobble together but there is other code helpful on the internet on the issue as it is a common problem one

    However my support for you is challenging each post from you has multiple topics/threads and often I can't see how it connects to what I last said. I'm gathering that you are fully focused on getting connected to USPS and aren't willing to take the interim step of getting Oauth working in your site in general to known test service, so let's stay with that, and I'll try to explain what you are seeing in their examples and how it relates to what I have written.

    I wrote

    The initial auth code request is a GET to a url on the remote service where the user logs in, authorizes the connection. This generates a code which is sent in a query parameter to the original site (Drupal) which then makes a POST to a token url with that code to get the token.

    And your reply is

    It sounds like you are saying it sends a GET request first, which the server you say sends back a response and then this client software sends a POST. Well the first GET sent will cause a FAIL - period - there will be no further communication as USPS has set it up

    If you look at the Example OAuth Client Credentials Token request on the USPS example page onb Github, there is one POST to the /token URL ad that is all - with the data sent in the body. That is what works, and all that works - so how is this module stopped from sending the intial GET request in favor of the POST wit the data in the body

    I am describing the process for the authorization code flow and you are responding about the client credentials flow. This module will not send the client credentials token request, or any token request in a GET since I am not overriding the default in the upstream library.

    It seems like you keep switching between grant flows. Let's pick one, stick with it, and get it working for you.

  • 🇺🇸United States bobburns

    Client credentials flow is all I have ever been seeking to get to work right now

    Early I posted the screen capture of the reject if a GET request is made on /token

    You explained the issue here

    "" I am describing the process for the authorization code flow and you are responding about the client credentials flow. This module will not send the client credentials token request, or any token request in a GET since I am not overriding the default in the upstream library.""

    So it sounds like you are now saying client credentials does not in the library send a GET request

    If it sends POST that is great, but it needs to be sent in the BODY not the HEADER

    A Basic Header Authorization will not work. There is no initial log in, just the POST as a client credentials grant with client ID and secret as a POST in the BODY

    I do not what you are talking about getting Oauth2 working with a reliable service. It already is working.

    So does the client credentials flow send only a POST by default, if so then the modification is only to send the data params in the body and not the header like the last thing I asked about the $options

  • 🇺🇸United States fathershawn New York

    So does the client credentials flow send only a POST by default??? If so then the modification is only to send the data params in the body and not the header like the last thing I asked about the $options section I posted of the example using your previous code solution.

    All of the token requests are POST by default.

    All of the POST requests send the data in the body, content 'application/x-www-form-urlencoded' by default. That is to say the request sends data as if it were submitting an HTML form. This is default Oauth behavior.

    I don't know why your plugin implementation is not working, but neither request method nor non-standard data transmission are not the reasons. You've talked about changing the corresponding configuration entity form. Have you done so? If so please revert your version of this module to the released state before we continue.

    Please post the current state of your plugin class.

  • 🇺🇸United States bobburns

    The form is the default and this is the plugin which fails

    <?php

    declare(strict_types=1);

    namespace Drupal\commerce_usps\Plugin\Oauth2Client;

    use Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginBase;
    use Drupal\oauth2_client\Plugin\Oauth2Client\StateTokenStorage;

    /**
    * Auth code
    *
    * @Oauth2Client(
    * id = "auth5code",
    * name = @Translation("Auth5Code token grant"),
    * grant_type = "client_credentials",
    * authorization_uri = "https://apis.usps.com//oauth2/v3/token",
    * token_uri = "https://apis.usps.com/oauth2/v3/token",
    * success_message = TRUE
    * )
    */
    class Auth5Code extends Oauth2ClientPluginBase {

    /*
    * This example assumes that the Drupal site is using a shared resource
    * from a third-party service that provides a service to all uses of the site.
    *
    * Storing a single AccessToken in state for the plugin shares access to the
    * external resource for ALL users of this plugin.
    */

    use StateTokenStorage;

    }

    I keep saying the client crendentials must be sent in the body - that is why it fails. They cannot be sent in the basic Authorization Header. My last stab at it above shows what I tried. Perhaps I called the client id and secret form variables wrong. But if sent in a header they will fail anyway - only are to be sent in the body

    If this module does not send a GET then there is a bug because USPS replies with an error thrown to the browser on an attempt at an authorization_code grant saying it does because the GET is not allowed

    A client credential fail throws no error - it just fails and hangs up on you no further info

    See the attached Postman setup where "client crendentials sent in body" is selected and works

  • 🇺🇸United States fathershawn New York

    I think I found the disconnect! The Oauth2 standard for client credentials states:

    The client makes a request to the token endpoint by adding the
    following parameters using the "application/x-www-form-urlencoded"
    format per Appendix B with a character encoding of UTF-8 in the HTTP
    request entity-body

    However, your reference to Postman prompted me to go look at the USPS examples in GitHub again. Both the Postman file and the curl example use a non-standard json content type:

    curl -X 'POST' 'https://apis.usps.com/oauth2/v3/token' \
         --header 'Content-Type: application/json' \
         --data '{
    		"client_id": "{{CLIENT_ID}}",
    		"client_secret": "{{CLIENT_SECRET}}",
    		"grant_type": "client_credentials"
    		}'
    

    This has to be the source of your failure and I'll post back code in a bit you can add to your plugin to override the standard setting.

  • 🇺🇸United States fathershawn New York

    The \League\OAuth2\Client\OptionProvider\PostAuthOptionProvider enforces the content type and encoding in the Oauth2 standard. We need to override that to set the json option, so you will want your own options provider class. Place this class in your module at src/OAuth2/Client/OptionProvider/UspsClientCredentialsOptionProvider.php, and be sure to set the namespace to the right value for your module.

    
    declare(strict_types=1);
    
    namespace Drupal\your_module\OAuth2\Client\OptionProvider;
    
    use Drupal\oauth2_client\OAuth2\Client\OptionProvider\ClientCredentialsOptionProvider;
    use Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginInterface;
    
    /**
     * An option provider which alters the token request content type.
     */
    class UspsClientCredentialsOptionProvider extends ClientCredentialsOptionProvider {
    
    
      /**
       * A string of scopes imploded from the Oauth2ClientPlugin.
       */
      private string $scopeOption;
    
      /**
       * {@inheritdoc}
       */
      public function __construct(Oauth2ClientPluginInterface $clientPlugin) {
        $scopes = $clientPlugin->getScopes();
        if (!empty($scopes)) {
          $this->scopeOption = implode($clientPlugin->getScopeSeparator(), $scopes);
        }
      }
    
      /**
       * {@inheritdoc}
       */
      public function getAccessTokenOptions($method, array $params): array {
        if (!empty($this->scopeOption)) {
          $params['scope'] = $this->scopeOption;
        }
        return ['json' => $params];
      }
    
    }
    

    Next, add an override in your plugin class. First add the proper use statement for your new options provider:

      use Drupal\your_module\OAuth2\Client\OptionProvider\UspsClientCredentialsOptionProvider;
    

    Then override the getProvider method.

      /**
       * {@inheritdoc}
       */
      public function getProvider(): AbstractProvider {
        $provider =parent::getProvider();
        $provider->setOptionProvider(new UspsClientCredentialsOptionProvider($this));
        return $provider;
      }
    
  • 🇺🇸United States bobburns

    Thank you for this; however, it does not like

      /**
       * {@inheritdoc}
       */
      public function getProvider(): AbstractProvider {
        $provider = parent::getProvider();
        $provider->setOptionProvider(new UspsClientCredentialsOptionProvider($this));
        return $provider;
      }

    In firefox it is just a blank white screen and in Chrome it say the site cannot handle the request

    Remove the code and the site will come up

  • 🇺🇸United States fathershawn New York

    Do you have database logging enabled? If not do you have access to PHP error logs? Can you post any errors shown in your logs?

  • 🇺🇸United States bobburns

    Enabled logging and rotated logs manually to clear the junk.

    Initially the plugin was missing a use statement for AbstractProvider

    I added both

    use League\OAuth2\Client\Provider\AbstractProvider;
    use League\OAuth2\Client\Provider\GenericProvider;

    The plugin would enable and run then throwing (actually it enabled with only AbstractProvider)

    League\OAuth2\Client\Provider\Exception\IdentityProviderException: invalid_request in League\OAuth2\Client\Provider\GenericProvider->checkResponse() (line 236 of /public_html/vendor/league/oauth2-client/src/Provider/GenericProvider.php)

    with the following partial backtrace

    #0 /public_html/vendor/league/oauth2-client/src/Provider/AbstractProvider.php(740): League\OAuth2\Client\Provider\GenericProvider->checkResponse(Object(GuzzleHttp\Psr7\Response), Array)
    #1 /public_html/vendor/league/oauth2-client/src/Provider/AbstractProvider.php(646): League\OAuth2\Client\Provider\AbstractProvider->getParsedResponse(Object(GuzzleHttp\Psr7\Request))
    #2 /public_html/modules/oauth2_client/src/Plugin/Oauth2GrantType/ClientCredentials.php(35): League\OAuth2\Client\Provider\AbstractProvider->getAccessToken(Object(League\OAuth2\Client\Grant\ClientCredentials), Array)
    #3 /public_html/modules/oauth2_client/src/Plugin/Oauth2Client/Oauth2ClientPluginBase.php(297): Drupal\oauth2_client\Plugin\Oauth2GrantType\ClientCredentials->getAccessToken(Object(Drupal\xxxxxxxxxxxxxxx\Plugin\Oauth2Client\Auth99Code))
    #4 /public_html/modules/oauth2_client/src/Service/Oauth2ClientService.php(44): Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginBase->getAccessToken(Object(Drupal\oauth2_client\OwnerCredentials))
    #5 /public_html/modules/oauth2_client/src/Form/Oauth2ClientForm.php(330): Drupal\oauth2_client\Service\Oauth2ClientService->getAccessToken('auth99code', Object(Drupal\oauth2_client\OwnerCredentials))
    #6 [internal function]: Drupal\oauth2_client\Form\Oauth2ClientForm->testToken(Array, Object(Drupal\Core\Form\FormState))
    #7 /public_html/core/lib/Drupal/Core/Form/FormSubmitter.php(129): call_user_func_array(Array, Array)

    Looks like a Guzzle issue, but why is it trying to call "OwnerCredentials" ???

  • 🇺🇸United States fathershawn New York

    Looks like a Guzzle issue, but why is it trying to call "OwnerCredentials" ???

    OwnerCredentials is a value object that contains the client id and secret. It is used to pass those values so that they don't get captured by logs unintentionally revealed.

    You are actually successfully communicating with USPS: https://developer.usps.com/oauth#tag/Resources/operation/post-token invalid_request is from them and the exception is thrown because an HTTP code 200 is not received.

    This module should log the exception in a way that includes the return code and that will give you more of a clue.

    But you may not need this last round of customizations. The documentation page I found and linked above shows application/json as a option not a requirement! In the right sidebar one can switch between that and the standard application/x-www-form-urlencoded so they do accept standard Oauth2.

    Scope is listed as optional in the standard, but maybe not for USPS?

    Note that they include scope in their example values:

    urlencoded:

    grant_type=client_credentials &client_id=123456789 &client_secret=A1B2C3D4E5 &scope=ResourceA+ResourceB+ResourceC
    

    json:

    {
      "grant_type": "client_credentials",
      "client_id": "123456789",
      "client_secret": "A1B2c3d4E5",
      "scope": "ResourceA ResourceB ResourceC"
    }

    So try adding a scope value or values to your plugin annotation.

  • 🇺🇸United States bobburns

    That is what I last had in #24 calling by variable

    Even using the actual text values it still throws

    League\OAuth2\Client\Provider\Exception\IdentityProviderException: invalid_request in League\OAuth2\Client\Provider\GenericProvider->checkResponse() (line 236 of /public_html/vendor/league/oauth2-client/src/Provider/GenericProvider.php)

    I believe it is continuing to send data in a header

  • 🇺🇸United States fathershawn New York

    That is what I last had in #24 calling by variable

    Your metadata is not correct in #24.

    believe it is continuing to send data in a header

    Can you point to where that is happening in my code or the upstream library? I'm not aware of such logic and will need to fix it if true.

    I have attached a module with example plugins for your use case. Please let me know if they work, and this back and forth is not producing a conclusion.

  • 🇺🇸United States bobburns

    My #24 example I said I likely called the client variables wrong. I did not look for an error in the code, I just do not see where it is put into or called into the body as a parameter. I where it is done in reverse sort of in https://www.drupal.org/project/oauth2_client/issues/3384244 💬 OptionProvidor overruled Closed: works as designed

    USPS support responded to me with this

    ""Please see explanations below for the individual fields in OAuth.
    • Scope: Only specified if you wish to have access to specific APIs. If omitted, all APIs configured to your App will be in scope.
    • Code: This is a merchants authorization code that is provided to customers by third party platforms. In your case, this can be omitted.
    • Redirect URI: This is the merchants URL to their platform. In your case, this can be omitted.
    • Grant Type: This will be the type of token you are requesting. In your case, please use client_credentials""

    So it is "scope" and not "scopes" but does not go into a client_credentials grant request regardless

    The production api example runs but shows no delivery of a token nor error tp the log or php error log while the test api example does not show in the plugin list at all. The production example throws nothing to the log

    Since Drupal uses Guzzle and Guzzle uses Curl - is it possible to build a plugin that make a curl call directly using the example from USPS??

    Until I get an initial access token, no other grant can be sought.

  • 🇺🇸United States fathershawn New York

    I just do not see where it is put into or called into the body as a parameter.

    You don't see it here as it is in the upstream library. Here's a GitHub link since I can't link to your code to the default options for POST, which is the default method: https://github.com/thephpleague/oauth2-client/blob/8cc8488e627ab17b71238ef4c09235e95f4d5068/src/OptionProvider/PostAuthOptionProvider.php#L30

    The upstream library expects Guzzle. You may be able to use custom code to implement something with curl but that's not the standard practice in PHP now. Guzzle is pretty ubiquitous. I've removed the scopes since that's optional and corrected the test plugin id.

  • 🇺🇸United States fathershawn New York

    I don't have a USPS account and I can't directly debug this. I don't have any other reports of client credentials not working. If it still doesn't work then either the URLs have to be wrong or you may have a typo in the client id or client secret. I can't think of anything else

  • 🇺🇸United States bobburns

    Yes thank you for all your hard work. In my #24 I zeroed in on the PostAuth file you mention. My belief was to override it for body not headers. If the $params has anything more than the id and secret as shown in the USPS example it will fail - possibly without comment response.

    The file that makes the API calls for USPS in my module uses Curl directly.

    The original plan was to modify it to make the token calls, but to do token management would re-create what Oauth2Client already does.

    I last worked on Oauth2 in Drupal 7 on a Stripe Connect flow project, and was I believe a character in the header which caused this same kind of isdue

    I am going to try some mods to the override, then building the client_credentials Plugin with my working curl code.

    Since Postman works, I know the problem is in the extra parameters being possibly included and still being sent possibly also in the header.

    I will report back later.

  • 🇺🇸United States fathershawn New York

    Sounds good. Please do report back on what you find.

    Do you know how to use Devel module functions or even better Xdebug to inspect values as the code executes?

    The request/response exchange you will want to examine is at vendor/league/oauth2-client/src/Provider/AbstractProvider.php:645-646

  • 🇺🇸United States bobburns

    In setting up the simple oauth module the Rest_ui "sees" the Oauth2_client as this

    OAuth2 Client (read-only) /entity/oauth2_client/{oauth2_client}: GET

    AGAIN the "GET" issue appears, not a POST.

  • 🇺🇸United States fathershawn New York

    That display has no relation to the function of this module. It is telling you how to obtain data about the configuration entities that this module creates using the REST API.

    I've traced the code for you showing that the token request method is a POST. I've now added a line to the automated test for that flow to the dev branch of this module to further verify:

    if ($request instanceof Request) {
          $this->assertEquals('POST', $request->getMethod());
          $body = [];
          parse_str($request->getBody()->getContents(), $body);
          $this->assertArrayHasKey('scope', $body);
          $this->assertEquals('test-1,test-2', $body['scope']);
        }
    

    This test passes with the additional assertion.

    I gave guidance on how and where to inspect what you are sending to USPS in #41. Did you try that?

  • 🇺🇸United States bobburns

    No, I do not have PHPStorm or Xdebug setup locally. I am running the tests directly on the server since they are harmless to just getting a oauth2_client flow going.

    I am stopped unable to see where 'clientId`, `clientSecret`, `redirectUri`, is picked up and set equal the saved ' clientId`, `clientSecret`, to from the form

      $credentials = [
              'client_id' => $values['client_id'],
              'client_secret' => $values['client_secret'],
    

    Because in AbstractProvider at line 634 it is defined as

    $params = [
                'client_id'     => $this->clientId,
                'client_secret' => $this->clientSecret,
                'redirect_uri'  => $this->redirectUri,
            ];

    The redirect uri is obviously called from the Plugin itself because it works to cast a rejection / fail

    I had not worked on it for a few days

  • 🇺🇸United States fathershawn New York

    You do not need to rewrite the module code. You do not really need to verify that the upstream package AbstractProvider actually sends the correct data because you can see that nearly 1,500 other Drupal sites are happily using it and its basic functions work. In addition, we use this upstream package because it is highly regarded, has been required via composer more than 88 million times in the PHP ecosystem and has thousands of stars on packagist.org.

    You need to be sure that you have configured and implemented your custom plugin correctly. You don't need to use Xdebug. You can also use the Devel or Kint module to display values from your code in the browser.

    You also should consider using a local development environment so that you can iterate more quickly on your code. There are several video tutorials. Here's one: https://www.youtube.com/watch?v=8TaL6UmOohc and documentation here of d.o: https://www.drupal.org/search/site/ddev

  • 🇺🇸United States fathershawn New York

    Also, we just launched more extensive documentation at https://project.pages.drupalcode.org/oauth2_client/

  • 🇺🇸United States bobburns

    Thank you, but there is nothing wrong with MY Drupal site. I have been working with Drupal since version 6. I am not to the point of debugging MY code, this is the module code not working. Simple oauth works, curl works, Postman works, oauth2_client does not.

    I am pointing out the upstream library calls "clientId" and "clientSecret" as variables in AbstractProvider so where in the Oauth2_client code is that set equal to "client_id" and "client_secret"???

    This is not my code, this is the Oauth2_client Plugin which only needs three things but has more one which is the url we know is working.. The YouTube video has nothing to do with this issue. It is the $params not being sent in the function with $options causing the issue and how whether in the header or in the body.

    I have debugged and discovered the request reaches USPS but is "invalid" as I do not see where and how it is sent in the body nor how the $params variable picks up "this->clientId " or "this->clientSecret" from the Oauth2_client module. Where does it exist set by the form in configFactory to be available for use by the upstream library???

  • 🇺🇸United States fathershawn New York

    Thank you, but there is nothing wrong with MY Drupal site. I have been working with Drupal since version 6. I am not to the point of debugging MY code, this is the module code not working. Simple oauth works, curl works, Postman works, oauth2_client does not.

    That's not a respectful answer to my attempts to help you. This module provides tools for you to build oauth2 clients. Correctly building and configuring the oauth2 client is your code, that I can neither access nor debug. I've also been working in Drupal since D6 and am 100% certain that I will make an unintentional mistake or bug in software that I create and have to find it. That is true of all of us.

    I'm going to walk you through how it uses the data that you have entered and the plugin definition that you have created to communicate. I hope that helps you focus your development efforts. I also want to assure someone else who come upon this issue that this module does what it sets out to do as you are implying that it does not in fact work properly.

    I don't know what else to do to help you but I will continue to answer specific questions about the code because that is who we are as a community. However I'm not going to respond to general assertions that this module is completely broken. It's not true.

    Walkthrough

    For this discussion, I'll use code that I've sent you:

    
    declare(strict_types=1);
    
    namespace Drupal\usps_api_example\Plugin\Oauth2Client;
    
    use Drupal\Core\StringTranslation\TranslatableMarkup;
    use Drupal\oauth2_client\Attribute\Oauth2Client;
    use Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginBase;
    use Drupal\oauth2_client\Plugin\Oauth2Client\StateTokenStorage;
    
    /**
     * Client credentials for USPS test endpoint.
     */
    #[Oauth2Client(
      id: 'usps_test_api',
      name: new TranslatableMarkup('USPS Test API'),
      grant_type: 'client_credentials',
      authorization_uri: 'https://apis-tem.usps.com/oauth2/v3/token',
      token_uri: 'https://apis-tem.usps.com/oauth2/v3/v3/token',
    )]
    class UspsTestApi extends Oauth2ClientPluginBase {
      use StateTokenStorage;
    }
    

    See https://project.pages.drupalcode.org/oauth2_client/creating-plugins/

    Without additional modules installed, an admin must browse to /admin/config/system/oauth2-client, enter the client id and client secret and save the values. These credentials are stored via the State system in your database.
    See https://project.pages.drupalcode.org/oauth2_client/secrets/

    This module provides a service which is used to obtain your token (See https://project.pages.drupalcode.org/oauth2_client/tokens/). Assume that $oauth2ClientService is an instance of Oauth2ClientService. This is not a resource owner grant so only the plugin id is needed as a parameter:

    $access_token = $oauth2ClientService->getAccessToken('usps_test_api);
    

    Here's the method being called:

    public function getAccessToken(string $pluginId, ?OwnerCredentials $credentials = NULL): ?AccessTokenInterface {
        try {
          $client = $this->getClient($pluginId);
          return $client->getAccessToken($credentials);
        }
        catch (InvalidOauth2ClientException $e) {
          Error::logException($this->logger, $e);
          return NULL;
        }
      }
    

    The plugin class above does not implement this method directly but depends on Oauth2ClientPluginBase. That code is below, I've added multiple comments for this walkthrough.

    public function getAccessToken(?OwnerCredentials $credentials = NULL): ?AccessTokenInterface {
    // First see if there is a token stored in the database.
        $accessToken = $this->retrieveAccessToken();
        if ($accessToken instanceof AccessTokenInterface) {
    // Found one, now is it expired?
          $expirationTimestamp = $accessToken->getExpires();
          $expired = !empty($expirationTimestamp) && $accessToken->hasExpired();
          if (!$expired) {
            return $accessToken;
          }
    // It is expired, do we also have a refresh token that we can use?
          $refreshToken = $accessToken->getRefreshToken();
          if (!empty($refreshToken)) {
            $accessToken = $this->refresh->getAccessToken($this);
            return $accessToken;
          }
    // It's expired.  We can't make a direct request for an auth code grant and we don't store owner credentials.
          if ($this->getGrantType() === 'authorization_code' || $this->getGrantType() === 'resource_owner') {
            throw new NonrenewableTokenException($this->getGrantType());
          }
        }
    // We didn't find a stored token, so get a new token based on grant type.
        if ($this->grantType instanceof GrantWithCredentialsInterface) {
          $this->grantType->setUsernamePassword($credentials);
        }
        $token = $this->grantType->getAccessToken($this);
        if ($token instanceof AccessTokenInterface) {
          $this->storeAccessToken($token);
          return $token;
        }
        return NULL;
      }
    

    Your token is using client credentials grant so we look to

    ClientCredentials::getAccessToken
    
      public function getAccessToken(Oauth2ClientPluginInterface $clientPlugin): ?AccessTokenInterface {
        $provider = $clientPlugin->getProvider();
        $optionProvider = $provider->getOptionProvider();
        // If the provider was just created, our OptionProvider must be set.
        if (!($optionProvider instanceof ClientCredentialsOptionProvider)) {
          $provider->setOptionProvider(new ClientCredentialsOptionProvider($clientPlugin));
        }
        try {
          return $provider->getAccessToken('client_credentials', $clientPlugin->getRequestOptions());
        }
        catch (\Exception $e) {
          // Failed to get the access token.
          Error::logException($this->logger, $e);
          return NULL;
        }
      }
    

    The ClientCredentialsOptionProvider adds scope, we'll come back to it when it actually gets used.

    The first line above calls the ::getProvider method in the plugin. The one we are using depends on the base class so what's called is

      public function getProvider(): AbstractProvider {
        return new GenericProvider(
          [
            'clientId' => $this->getClientId(),
            'clientSecret' => $this->getClientSecret(),
            'redirectUri' => $this->getRedirectUri(),
            'urlAuthorize' => $this->getAuthorizationUri(),
            'urlAccessToken' => $this->getTokenUri(),
            'urlResourceOwnerDetails' => $this->getResourceUri(),
            'scopes' => $this->getScopes(),
            'scopeSeparator' => $this->getScopeSeparator(),
          ],
          $this->getCollaborators()
        );
      }
    

    Here your plugin retrieved the client id and client secret from the database, and the urls and scopes from your plugin definition and passes all of that into an instance of GenericProvider from the league/oauth2-client library.

    GenericProvider uses the parent AbstractProvider::getAccessToken

    public function getAccessToken($grant, array $options = [])
        {
            $grant = $this->verifyGrant($grant);
    
            if (empty($options['scope'])) {
                $options['scope'] = $this->getDefaultScopes();
            }
    
            if (is_array($options['scope'])) {
                $separator = $this->getScopeSeparator();
                $options['scope'] = implode($separator, $options['scope']);
            }
    
            $params = [
                'client_id'     => $this->clientId,
                'client_secret' => $this->clientSecret,
                'redirect_uri'  => $this->redirectUri,
            ];
    
            if (!empty($this->pkceCode)) {
                $params['code_verifier'] = $this->pkceCode;
            }
    
            $params   = $grant->prepareRequestParameters($params, $options);
            $request  = $this->getAccessTokenRequest($params);
            $response = $this->getParsedResponse($request);
            if (false === is_array($response)) {
                throw new UnexpectedValueException(
                    'Invalid response received from Authorization Server. Expected JSON.'
                );
            }
            $prepared = $this->prepareAccessTokenResponse($response);
            $token    = $this->createAccessToken($prepared, $grant);
    
            return $token;
        }
    

    Here your data gets prepared. This helper method gets called:

    protected function getAccessTokenRequest(array $params)
        {
            $method  = $this->getAccessTokenMethod();
            $url     = $this->getAccessTokenUrl($params);
            $options = $this->optionProvider->getAccessTokenOptions($this->getAccessTokenMethod(), $params);
    
            return $this->getRequest($method, $url, $options);
        }
    

    The default method is POST

        protected function getAccessTokenMethod()
        {
            return self::METHOD_POST;
        }
    

    The url is the token url that was set from your plugin definition.
    The options are set in this case by the ClientCredentialsOptionProvider::getAccessTokenOptions which adds scope to the parameters, and calls \League\OAuth2\Client\OptionProvider\PostAuthOptionProvider::getAccessTokenOptions

        public function getAccessTokenOptions($method, array $params)
        {
            $options = ['headers' => ['content-type' => 'application/x-www-form-urlencoded']];
    
            if ($method === AbstractProvider::METHOD_POST) {
                $options['body'] = $this->getAccessTokenBody($params);
            }
    
            return $options;
        }
    

    Which sets the content type and encodes the values for the request body.

    This is all sent to a request factory and sent back from getAccessTokenRequest as a request object.

    We are now to ::getParsedResponse in the code above which calls ::getResponse which uses a Guzzle client to send the request.

        public function getResponse(RequestInterface $request)
        {
            return $this->getHttpClient()->send($request);
        }
    

    If JSON is returned it is parsed and turned into an AccessToken object. Otherwise errors are thrown.

  • 🇺🇸United States bobburns

    Yes, thank you as that is how I read it BUT. where is the client_id and client_secret set from the form to clientId and clientSecret for.

    $params = [
                'client_id'     => $this->clientId,
                'client_secret' => $this->clientSecret,

    I do not see how values are passed by the Plugin without this variable declaration somewhere from the form values.

    It also appears that this is still sent in a URL header which will fail

  • 🇺🇸United States fathershawn New York

    Your credentials are stored based on from submission. See \Drupal\oauth2_client\Form\Oauth2ClientForm::submitForm

     public function submitForm(array &$form, FormStateInterface $form_state): void {
        // Prepare the credential data for storage and save in the entity,.
        $values = $form_state->getValues();
        $provider = $values['credential_provider'];
        // Set default storage key.
        $key = $this->entity->uuid() ?? $form['#build_id'];
        switch ($provider) {
          case 'oauth2_client':
            $credentials = [
              'client_id' => $values['client_id'],
              'client_secret' => $values['client_secret'],
            ];
            $this->state->set($key, $credentials);
            break;
    
          case 'key':
            $key = $values['key_id'];
        }
        $form_state->setValue('credential_storage_key', $key);
        parent::submitForm($form, $form_state);
      }
    

    And then \Drupal\oauth2_client\Service\CredentialProvider::getCredentials is used to retrieve those values.

      public function getCredentials(Oauth2ClientPluginInterface $plugin): array {
        /** @var \Drupal\oauth2_client\Entity\Oauth2Client $config */
        $config = $this->entityTypeManager->getStorage('oauth2_client')->load($plugin->getId());
        $credentialProvider = $config->getCredentialProvider();
        $storageKey = $config->getCredentialStorageKey();
        $credentials = [];
        if (empty($credentialProvider) || empty($storageKey)) {
          return $credentials;
        }
        switch ($credentialProvider) {
          case 'key':
            $keyEntity = $this->keyRepository->getKey($storageKey);
            if ($keyEntity instanceof Key) {
              // A key was found in the repository.
              $credentials = $keyEntity->getKeyValues();
            }
            break;
    
          default:
            $credentials = $this->state->get($storageKey);
        }
    
        return $credentials ?? [];
      }
    

    This service is called in the base plugin getters for these values. For example:

      /**
       * Helper function to retrieve and cache credentials.
       *
       * @return string[]
       *   The credentials array.
       */
      private function retrieveCredentials(): array {
        if (empty($this->credentials)) {
          $this->credentials = $this->credentialService->getCredentials($this);
        }
        return $this->credentials;
      }
    
    It also appears that this is still sent in a URL header string which will fail.

    Can you point to code that supports this assertion? I've now twice posted the code showing that parameters are sent as form values in the body via POST.

  • 🇺🇸United States bobburns

    And my point is NOWHERE is "clientId" and "clientSecret" declared or set equal to the form values "client_id" or "client_secret"

    Thus it appears there is no value for "clientId" or "clientSecret" for the $params value to use but maybe "null"

    There is a line that sets a string for use to send as a header but right now that above I have raised four times to no address

    NOWHERE is "clientId" and "clientSecret" declared or set equal to the form values "client_id" or "client_secret" I can find nor have you said

    When I mentioned modifying the form to save those values you said it not needed to be done

  • 🇺🇸United States fathershawn New York

    All caps is shouting in text interchange. I think you must know that, so please stop.

    I'll explain in more detail about client id and secret.

    You can see in my first code block in #50 that these values are put in an associative array and stored in the database as key-value pair based on your plugin.

    My last code block shows these values being retrieved from this storage and cached locally in the credentials array. I said this was used by the getters, so let me post an explicit example for client id. There is a matching method for client secret.

      /**
       * {@inheritdoc}
       */
      public function getClientId(): string {
        $credentials = $this->retrieveCredentials();
        return $credentials['client_id'] ?? '';
      }
    

    I previously posted where this method is called in the constructor within Oauth2ClientPluginBase::getProvider

            'clientId' => $this->getClientId(),
    

    This gets stored by the constructor of GenericProvider in the clientId property.

    public function __construct(array $options = [], array $collaborators = [])
        {
            $this->assertRequiredOptions($options);
    
            $possible   = $this->getConfigurableOptions();
            $configured = array_intersect_key($options, array_flip($possible));
    
            foreach ($configured as $key => $value) {
                $this->$key = $value;
            }
    
            // Remove all options that are only used locally
            $options = array_diff_key($options, $configured);
    
            parent::__construct($options, $collaborators);
        }
    

    I posted the code for \League\OAuth2\Client\Provider\AbstractProvider::getAccessToken in #48 which shows that the clientId property is stored into the $params array. I also showed the code in #48 of how this value gets encoded and placed as the body of the request.

    There is a line that sets a string for use to send as a header but right now that above I have raised four times to no address

    You have posted multiple times about a basic authorization scheme class from the upstream library that is not used in this module. A developer could code one of our plugins to use but that class. That is not the case by default.

  • 🇺🇸United States bobburns

    I see ok thanks. I will try a plugin built with curl calling clientId and clientSecret just as the USPS example shows. I found it in Oauth2ClientPluginBase all the way at the bottom along with a host of other things defined

    I will contact USPS again and ask what throws an "invalid_request" response

    The custom code you provided did not run until I added the AbstractProvider as a use statement, so I will add GenericProvider as a use statement also

    USPS told me anything more than what the example shows sent in the client_credentials request will cause a fail as did the "authorization_code" fail with a "GET" error thrown to the URL window and the browser in my #10

    I believe where I saw the string issue was in a question in a github support thread https://github.com/thephpleague/oauth2-client/issues/774#issuecomment-64...

  • 🇺🇸United States fathershawn New York

    USPS told me anything more than what the example shows sent in the client_credentials request will cause a fail

    There's the issue then and it is my code. We have

    redirect_code<code> as required and it's sending that when along with your other data.  I'm going post below how you can work around it.  Or you can extend AbstractProvider and fully customize by using your provider class in this function.
    
    Add the provider classes to your your <code>Oauth2Client

    plugin class:

    use League\OAuth2\Client\Provider\AbstractProvider;
    use League\OAuth2\Client\Provider\GenericProvider;
    

    The method below omits scope and all other body data except your credentials. urlResourceOwnerDetails is required by GenericProvider but will be dropped before the request if empty. Add it to your Oauth2Client plugin class to override behavior in Oauth2ClientPluginBase

      /**
       * Creates a new provider object.
       *
       * @return \League\OAuth2\Client\Provider\GenericProvider
       *   The provider of the OAuth2 Server.
       */
      public function getProvider(): AbstractProvider {
        return new GenericProvider(
          [
            'clientId' => $this->getClientId(),
            'clientSecret' => $this->getClientSecret(),
            'urlAuthorize' => $this->getAuthorizationUri(),
            'urlAccessToken' => $this->getTokenUri(),
            'urlResourceOwnerDetails' => $this->getResourceUri(),
          ],
          $this->getCollaborators()
        );
      }
    

    This results in the following data prepared for the request in my local testing:

    [
      "headers" =>  [
        "content-type" => "application/x-www-form-urlencoded"
      ]
      "body" => "client_id=foo&client_secret=bar&grant_type=client_credentials"
    ]
    
  • 🇺🇸United States fathershawn New York
  • 🇺🇸United States bobburns

    Thank you. I added the function to two of the plugins, but the oath2_client logger is gone, so I went to uninstall and re-install the module

    Not related to the function you provided, on uninstall the module throws this

    Error: Call to a member function getPath() on null in Drupal\config_translation\ConfigNamesMapper->getOverviewRoute() (line 247 of core/modules/config_translation/src/ConfigNamesMapper.php).
    Drupal\config_translation\Routing\RouteSubscriber->alterRoutes(Object) (Line: 37)
    Drupal\Core\Routing\RouteSubscriberBase->onAlterRoutes(Object, 'routing.route_alter', Object)
    call_user_func(Array, Object, 'routing.route_alter', Object) (Line: 111)
    Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'routing.route_alter') (Line: 189)
    Drupal\Core\Routing\RouteBuilder->rebuild() (Line: 83)
    Drupal\Core\ProxyClass\Routing\RouteBuilder->rebuild() (Line: 558)
    Drupal\Core\Extension\ModuleInstaller->uninstall(Array, 1) (Line: 91)
    Drupal\Core\ProxyClass\Extension\ModuleInstaller->uninstall(Array) (Line: 182)
    Drupal\system\Form\ModulesUninstallConfirmForm->submitForm(Array, Object)
    call_user_func_array(Array, Array) (Line: 129)
    Drupal\Core\Form\FormSubmitter->executeSubmitHandlers(Array, Object) (Line: 67)
    Drupal\Core\Form\FormSubmitter->doSubmitForm(Array, Object) (Line: 597)
    Drupal\Core\Form\FormBuilder->processForm('system_modules_uninstall_confirm_form', Array, Object) (Line: 326)
    Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 73)
    Drupal\Core\Controller\FormController->getContentResult(Object, Object)
    call_user_func_array(Array, Array) (Line: 123)

    more

  • 🇺🇸United States fathershawn New York

    I don't know what to advise you about that error. This module does not alter any routes. I just uninstalled the module in my development site without error.

  • 🇺🇸United States fathershawn New York

    This module does provide the route described in those issues

    entity.oauth2_client.edit_form:
      path: '/admin/config/system/oauth2-client/{oauth2_client}/edit'
      defaults:
        _entity_form: 'oauth2_client.edit'
        _title: 'Edit an oauth2 client'
      requirements:
        _permission: 'administer oauth2 clients'
    
    Still and yet somehow on re install no entity is created for oauth2_client

    Oauth2ClientListBuilder triggers discovery on load. I can't reproduce this and do not have any way to know why additional plugins you have created are not discovering properly. I'm assuming that you have tried clearing cache as the traditional cure-all.

  • 🇺🇸United States fathershawn New York
  • 🇺🇸United States bobburns

    OK Thanks, so I wont uninstall it again. The multi-lingal config_translation causes that truncated uninstall.

    Right now there is no oauth2_client logger filter in the Drupal log anymore

    I can get the Access.php plugin to throw the "GET not allowed" error so I know that plug in is communicating with USPS

    But no longer does the client credentials plugin do anything. It does not throw an error to the PHP error log,indicating an "invalid_request" showing it was communicating with USPS

    I will need to work backwards to get back to at least that error to prove it is communicating and then try the most recent function you provided.

    Currently this is the plugin below but not even show signs of life it is sending anything

    <?php
    
    declare(strict_types=1);
    
    namespace Drupal\xxxxxxxxxxxxxxx\Plugin\Oauth2Client;
    
    use Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginBase;
    use Drupal\oauth2_client\Plugin\Oauth2Client\StateTokenStorage;
    use League\OAuth2\Client\Provider\AbstractProvider;
    use League\OAuth2\Client\Provider\GenericProvider;
    
    /**
     * @Oauth2Client(
     *   id = "auth909code",
     *   name = @Translation("Auth909Code token grant"),
     *   grant_type = "client_credentials",
     *   authorization_uri = "https://apis.usps.com//oauth2/v3/token",
     *   token_uri = "https://apis.usps.com/oauth2/v3/token",
     *   success_message = TRUE
     * )
     */
    class Auth909Code extends Oauth2ClientPluginBase {
      /**
       * Creates a new provider object.
       *
       * @return \League\OAuth2\Client\Provider\GenericProvider
       *   The provider of the OAuth2 Server.
       */
      public function getProvider(): AbstractProvider {
        return new GenericProvider(
          [
            'clientId' => $this->getClientId(),
            'clientSecret' => $this->getClientSecret(),
            'urlAuthorize' => $this->getAuthorizationUri(),
            'urlAccessToken' => $this->getTokenUri(),
            'urlResourceOwnerDetails' => $this->getResourceUri(),
          ],
          $this->getCollaborators()
        );
      }
    
      use StateTokenStorage;
    
     }
  • 🇺🇸United States fathershawn New York

    Please add the Devel module to your test site:

    and enable the devel module

    Then edit the code brought down by composer. Add a call to ksm() in the try block of this function:

        /**
         * Sends a request and returns the parsed response.
         *
         * @param  RequestInterface $request
         * @return mixed
         * @throws IdentityProviderException
         * @throws UnexpectedValueException
         * @throws GuzzleException
         */
        public function getParsedResponse(RequestInterface $request)
        {
            try {
                $response = $this->getResponse($request);
    ksm($response);
            } catch (BadResponseException $e) {
                $response = $e->getResponse();
            }
    
            $parsed = $this->parseResponse($response);
    
            $this->checkResponse($response, $parsed);
    
            return $parsed;
        }
    

    You will find this in your site at vendor/league/oauth2-client/src/Provider/AbstractProvider.php. This will output a drupal message on the page with the response recieved from USPS after you hit the "Save and request token" button on your plugin's form.

  • 🇺🇸United States bobburns

    I have Devel installed. I added the ksm($response); where shown. Nothing outputs to the screen when the plugin is run

    I restored the code you posted in #30, and it runs, communicates with USPS and gets the "invalid_request" response from USPS, but it too does not output to the screen, but does throw to the Drupal log under "oauth2_client" filter

    I even tried

    namespace Drupal\xxxxxxxxxxx\Plugin\Oauth2Client;
    
    use Drupal\xxxxxxxxxxxxx\OAuth2\Client\OptionProvider\UspsClientCredentialsOptionProvider;
    use Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginBase;
    use Drupal\oauth2_client\Plugin\Oauth2Client\StateTokenStorage;
    use League\OAuth2\Client\Provider\AbstractProvider;
    use League\OAuth2\Client\Provider\GenericProvider;
    
    /**
     *
     * @Oauth2Client(
     *   id = "auth1999code",
     *   name = @Translation("Auth1999Code token grant"),
     *   grant_type = "client_credentials",
     *   authorization_uri = "https://apis.usps.com//oauth2/v3/token",
     *   token_uri = "https://apis.usps.com/oauth2/v3/token",
     *   success_message = TRUE
     * )
     */
    class Auth1999Code extends Oauth2ClientPluginBase {
    
      use StateTokenStorage;
    
      /**
       * {@inheritdoc}
       */
      public function getProvider(): AbstractProvider {
        $provider =parent::getProvider();
        $provider->setOptionProvider(new UspsClientCredentialsOptionProvider($this));
        return new GenericProvider(
          [
            'clientId' => $this->getClientId(),
            'clientSecret' => $this->getClientSecret(),
            'urlAuthorize' => $this->getAuthorizationUri(),
            'urlAccessToken' => $this->getTokenUri(),
            'urlResourceOwnerDetails' => $this->getResourceUri(),
          ],
          $this->getCollaborators()
        );
        return $provider;
      }
    
     }

    which runs without error, but no response

    Something in the function code $this->getCollaborators() is it looks like causing no request to ever be sent

  • 🇺🇸United States fathershawn New York

    You have two return statements in your getProvider code in #63

    Maybe try inserting calls to ksm earlier in the stack after you fix that. You should be able to use that to find where it is failing

  • 🇺🇸United States bobburns

    If I change the class to

    class Auth1999Code extends Oauth2ClientPluginBase {
    
      use StateTokenStorage;
    
      /**
       * {@inheritdoc}
       */
      public function getProvider(): AbstractProvider {
        $provider =parent::getProvider();
        $provider->setOptionProvider(new UspsClientCredentialsOptionProvider($this));
        new GenericProvider(
          [
            'clientId' => $this->getClientId(),
            'clientSecret' => $this->getClientSecret(),
            'urlAuthorize' => $this->getAuthorizationUri(),
            'urlAccessToken' => $this->getTokenUri(),
            'urlResourceOwnerDetails' => $this->getResourceUri(),
          ],
          $this->getCollaborators()
        );
        return $provider;
      }
    
     }

    It runs and returns the "invalid_request" error to the Drupal log messages under oauth2_client. That error can be anything and the USPS response will not indentify it other than contains "extra parameters" One such parameter is the scope not allowed in the client_credentials grant request

    The UspsClientCredentialsOptionProvider you provided runs the request but contains code relating to scope which will cause the failure as "invalid_request" I used it because it ran to present at least the invalid_request response, but I do not know how to use it to not present the scope. You say in #34 it sends a scope parameter

  • 🇺🇸United States fathershawn New York

    Making multiple large changes to your code is not the best way to debug. Single changes and test will get you where you want to be.

    You didn't get a message in #62 likely because USPS returned an error. So let's move to this:

        public function getParsedResponse(RequestInterface $request)
        {
            try {
                $response = $this->getResponse($request);
    ksm($response->getBody()->getContents());
            } catch (BadResponseException $e) {
                $response = $e->getResponse();
    ksm($response->getBody()->getContents());
            }
    
            $parsed = $this->parseResponse($response);
    
            $this->checkResponse($response, $parsed);
    
            return $parsed;
        }
    

    With this in place, and this plugin (Also uploaded in a zip file as example 3)

    <?php
    
    declare(strict_types=1);
    
    namespace Drupal\usps_api_example\Plugin\Oauth2Client;
    
    use Drupal\Core\StringTranslation\TranslatableMarkup;
    use Drupal\oauth2_client\Attribute\Oauth2Client;
    use Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginBase;
    use Drupal\oauth2_client\Plugin\Oauth2Client\StateTokenStorage;
    use League\OAuth2\Client\Provider\AbstractProvider;
    use League\OAuth2\Client\Provider\GenericProvider;
    
    /**
     * Client credentials for USPS test endpoint.
     */
    #[Oauth2Client(
      id: 'usps_test_api',
      name: new TranslatableMarkup('USPS Test API'),
      grant_type: 'client_credentials',
      authorization_uri: 'https://apis-tem.usps.com/oauth2/v3/token',
      token_uri: 'https://apis-tem.usps.com/oauth2/v3/token',
    )]
    class UspsTestApi extends Oauth2ClientPluginBase {
      use StateTokenStorage;
    
      /**
       * Creates a new provider object.
       *
       * @return \League\OAuth2\Client\Provider\GenericProvider
       *   The provider of the OAuth2 Server.
       */
      public function getProvider(): AbstractProvider {
        return new GenericProvider(
          [
            'clientId' => $this->getClientId(),
            'clientSecret' => $this->getClientSecret(),
            'urlAuthorize' => $this->getAuthorizationUri(),
            'urlAccessToken' => $this->getTokenUri(),
            'urlResourceOwnerDetails' => $this->getResourceUri(),
          ],
          $this->getCollaborators()
        );
      }
    
    }
    

    I get this message after clicking "Save and request token"

  • 🇺🇸United States bobburns

    I duplicated the AbstractProvider.php to AbstractProvider_php and modified the original by dropping in the new ksm code. No thing still is thrown to the screen

    Example_api plugin named to UspsTestApi.php runs but throws no message to the screen or the Drupal log

    The original file I mentioned still runs and throws the invalid_request to the Drupal log

    "invalid_client" means as much as "invalid_request" - a connection that failed but the example api does not throw anything for me to the screen or the Drupal log

    The #[ oauth2client ] method does not throw any mesage to the Drupal log. The @oauth2Client method does

    UspsClientCredentialsOptionProvider.php is the only thing that appears to make a connection to USPS other than a naked plugin set to throw an error

    I am not versed on this league library, but I put the function in UspsClientCredentialsOptionProvider.php and it runs and throws the same invalid request error with no description. I cannot see how the getProvider function does anything

    One of the plugins complained about a syntax error and trapped me to an uninstall - re install scenario throwing the same config translation null error

  • 🇺🇸United States fathershawn New York

    If you have a ksm() call in both sides of the try/catch as shown in AbstractProvider then something is very much off with how the code is running in your setup. That's a dual path code branch and so one of them runs.

    I can't tell more from here. Is there a Drupal community where you are? Someone you could pair with to debug this with you?

  • 🇺🇸United States bobburns

    My site is multi domain with domain access using Adaptive Theme which has generated themes and has done strange thing like this before. I am going to try changing the theme first, because at the very least the file that runs should output more info

  • 🇺🇸United States bobburns

    Looking at the status message, it appeared to be thrown to a terminal window so investigating the ksm command I discovered it is no longer a part of the Devel module. See https://www.drupal.org/project/devel_kint_extras/issues/3458022 🐛 ksm() not working with Drupal 10.3.0 Closed: won't fix

    It is now separate as the "devel-kint-extras" module. I have installed it but it appears also requires Kint also

    It also appears that the override code prevents the http request - except in the case of uspsclientcredentials file which in returning "json" is making the http request

    I would think fixing what is half working is better than starting over with non working override

    The current file just needs to remove and block the scope in place of what this latest function is supposed to do according to # 66

  • 🇺🇸United States fathershawn New York

    What I posted in #66 was the result of enabling the attached module in a new install of Drupal and altering the AbstractProvider code as shown.

    I used the latest version of Devel which has this backwards compatibility code: https://gitlab.com/drupalspoons/devel/-/blob/5.x/devel.module?ref_type=h... but any of Devel's methods of outputing data would probably work. I use other methods to inspect values at runtime but this was my best idea.

    I entered dummy client ID and secret and did a save and test. USPS responded with a message about improper credentials. What I posted works correctly, it just needs a real id and secret. If it is not working in your situation there must be some interaction with other code or the environment. I can't think of anything else.

  • 🇺🇸United States bobburns

    I am talking about the files from #30 to be modified like what #66 was modified as an override to send. It was the last client that sends and gets a response

  • 🇺🇸United States fathershawn New York

    It was the last client that sends and gets a response

    I have different results. In a clean install of Drupal, using latest version of Devel, the example plugin is both posted and uploaded in #66 along with inserting debugging functions in both sides of the code branch within getParsedResponse also posted in #66, I get an expected denial response from USPS, also as posted when using random strings as client id and client secret.

    I can try to add some more debugging output built into the module, but you could one of Devel's other functions there such dpm() instead.

  • 🇺🇸United States bobburns

    OK I got ksm working - it threw

    Error: Object of class Drupal\kint\DrupalFieldableEntityPlugin could not be converted to string in array_diff() (line 19 of modules/devel_kint_extras/src/Plugin/Devel/Dumper/KintExtended.php).
    Drupal\devel_kint_extras\Plugin\Devel\Dumper\KintExtended->configure() (Line: 31)
    Drupal\devel\Plugin\Devel\Dumper\Kint->__construct(Array, 'kint', Array) (Line: 25)
    Drupal\Core\Plugin\Factory\ContainerFactory->createInstance('kint', Array) (Line: 76)
    Drupal\Component\Plugin\PluginManagerBase->createInstance('kint', Array) (Line: 62)
    Drupal\devel\DevelDumperPluginManager->createInstance('kint') (Line: 97)
    Drupal\devel\DevelDumperManager->createInstance('kint') (Line: 121)
    Drupal\devel\DevelDumperManager->export('
    {
    "error": "invalid_request",
    "error_description": "The Content-Type header is either missing or specifies a media type that is not supported.",
    "error_uri": "https://datatracker.ietf.org/doc/html/rfc6749#page-45"
    }
    ', NULL, 'kint', ) (Line: 129)
    Drupal\devel\DevelDumperManager->message('
    {
    "error": "invalid_request",
    "error_description": "The Content-Type header is either missing or specifies a media type that is not supported.",
    "error_uri": "https://datatracker.ietf.org/doc/html/rfc6749#page-45"
    }
    ', NULL, 'status', 'kint') (Line: 453)
    ksm('
    {
    "error": "invalid_request",
    "error_description": "The Content-Type header is either missing or specifies a media type that is not supported.",
    "error_uri": "https://datatracker.ietf.org/doc/html/rfc6749#page-45"
    }
    ') (Line: 737)
    League\OAuth2\Client\Provider\AbstractProvider->getParsedResponse(Object) (Line: 646)
    League\OAuth2\Client\Provider\AbstractProvider->getAccessToken(Object, Array) (Line: 35)
    Drupal\oauth2_client\Plugin\Oauth2GrantType\ClientCredentials->getAccessToken(Object) (Line: 297)
    Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginBase->getAccessToken(Object) (Line: 44)
    Drupal\oauth2_client\Service\Oauth2ClientService->getAccessToken('auth999code', Object) (Line: 330)
    Drupal\oauth2_client\Form\Oauth2ClientForm->testToken(Array, Object)
    call_user_func_array(Array, Array) (Line: 129)

    ... more

    This was from a plugin of this structure

    <?php
    
    declare(strict_types=1);
    
    namespace Drupal\xxxxxxxxxxxx\Plugin\Oauth2Client;
    
    use Drupal\xxxxxxxxxxx\OAuth2\Client\OptionProvider\UspsClientCredentialsOptionProvider;
    use Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginBase;
    use Drupal\oauth2_client\Plugin\Oauth2Client\StateTokenStorage;
    use League\OAuth2\Client\Provider\AbstractProvider;
    use League\OAuth2\Client\Provider\GenericProvider;
    
    /**
     *
     * @Oauth2Client(
     *   id = "auth999code",
     *   name = @Translation("Auth999Code token grant"),
     *   grant_type = "client_credentials",
     *   authorization_uri = "https://apis.usps.com//oauth2/v3/token",
     *   token_uri = "https://apis.usps.com/oauth2/v3/token",
     *   success_message = TRUE
     * )
     */
    class Auth999Code extends Oauth2ClientPluginBase {
    
      use StateTokenStorage;
    
      /**
       * {@inheritdoc}
       */
      public function getProvider(): AbstractProvider {
        $provider =parent::getProvider();
        $provider->setOptionProvider(new UspsClientCredentialsOptionProvider($this));
        return $provider;
      }
    
     }

    which is the code structure from #30 and Drupal\xxxxxxxxxxx\OAuth2\Client\OptionProvider\UspsClientCredentialsOptionProvider
    is

    <?php
    
    declare(strict_types=1);
    
    namespace Drupal\xxxxxxxxxx\OAuth2\Client\OptionProvider;
    
    use Drupal\oauth2_client\OAuth2\Client\OptionProvider\ClientCredentialsOptionProvider;
    use Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginInterface;
    
    /**
     * An option provider which alters the token request content type.
     */
    class UspsClientCredentialsOptionProvider extends ClientCredentialsOptionProvider {
    
      /**
       * Creates a new provider object.
       *
       * @return \League\OAuth2\Client\Provider\GenericProvider
       *   The provider of the OAuth2 Server.
       */
      public function getProvider(): AbstractProvider {
        return new GenericProvider(
          [
            'clientId' => $this->getClientId(),
            'clientSecret' => $this->getClientSecret(),
            'urlAuthorize' => $this->getAuthorizationUri(),
            'urlAccessToken' => $this->getTokenUri(),
            'urlResourceOwnerDetails' => $this->getResourceUri(),
          ],
          $this->getCollaborators()
        );
      }
    
      /**
       * A string of scopes imploded from the Oauth2ClientPlugin.
       */
      private string $scopeOption;
    
      /**
       * {@inheritdoc}
       */
      public function __construct(Oauth2ClientPluginInterface $clientPlugin) {
        $scopes = $clientPlugin->getScopes();
        if (!empty($scopes)) {
          $this->scopeOption = implode($clientPlugin->getScopeSeparator(), $scopes);
        }
      }
    
      /**
       * {@inheritdoc}
       */
      public function getAccessTokenOptions($method, array $params): array {
        if (!empty($this->scopeOption)) {
          $params['scope'] = $this->scopeOption;
        }
        return ['json' => $params];
      }
    
    }

    whidh I admit is hacked up code that I did just to throw something of a more detailed response; however the original unaltered code from #30 throws the same thing

    <?php
    
    declare(strict_types=1);
    
    namespace Drupal\xxxxxxxxxxxxxxx\OAuth2\Client\OptionProvider;
    
    use Drupal\oauth2_client\OAuth2\Client\OptionProvider\ClientCredentialsOptionProvider;
    use Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginInterface;
    
    /**
     * An option provider which alters the token request content type.
     */
    class UspsClientCredentialsOptionProvider extends ClientCredentialsOptionProvider {
    
    
      /**
       * A string of scopes imploded from the Oauth2ClientPlugin.
       */
      private string $scopeOption;
    
      /**
       * {@inheritdoc}
       */
      public function __construct(Oauth2ClientPluginInterface $clientPlugin) {
        $scopes = $clientPlugin->getScopes();
        if (!empty($scopes)) {
          $this->scopeOption = implode($clientPlugin->getScopeSeparator(), $scopes);
        }
      }
    
      /**
       * {@inheritdoc}
       */
      public function getAccessTokenOptions($method, array $params): array {
        if (!empty($this->scopeOption)) {
          $params['scope'] = $this->scopeOption;
        }
        return ['json' => $params];
      }
    
    }

    The straight example edited with no override as

    <?php
    
    declare(strict_types=1);
    
    namespace Drupal\xxxxxxxxxxxxxxxxxx\Plugin\Oauth2Client;
    
    use Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginBase;
    use Drupal\oauth2_client\Plugin\Oauth2Client\StateTokenStorage;
    
    /**
     * Auth code 
     *
     * @Oauth2Client(
     *   id = "authcode",
     *   name = @Translation("AuthCode token grant"),
     *   grant_type = "client_credentials",
     *   authorization_uri = "https://apis.usps.com/oauth2/v3/token",
     *   token_uri = "https://apis.usps.com/oauth2/v3/token",
     *   success_message = TRUE
     * )
     */
    class AuthCode extends Oauth2ClientPluginBase {
    
      /*
       * This example assumes that the Drupal site is using a shared resource
       * from a third-party service that provides a service to all uses of the site.
       *
       * Storing a single AccessToken in state for the plugin shares access to the
       * external resource for ALL users of this plugin.
       */
    
      use StateTokenStorage;
    
     }

    actually works throwing (three identical)

    Error: Object of class Drupal\kint\DrupalFieldableEntityPlugin could not be converted to string in array_diff() (line 19 of modules/devel_kint_extras/src/Plugin/Devel/Dumper/KintExtended.php).
    Drupal\devel_kint_extras\Plugin\Devel\Dumper\KintExtended->configure() (Line: 31)
    Drupal\devel\Plugin\Devel\Dumper\Kint->__construct(Array, 'kint', Array) (Line: 25)
    Drupal\Core\Plugin\Factory\ContainerFactory->createInstance('kint', Array) (Line: 76)
    Drupal\Component\Plugin\PluginManagerBase->createInstance('kint', Array) (Line: 62)
    Drupal\devel\DevelDumperPluginManager->createInstance('kint') (Line: 97)
    Drupal\devel\DevelDumperManager->createInstance('kint') (Line: 121)
    Drupal\devel\DevelDumperManager->export('
    {
        "access_token": "eyJraWQiOiJIdWpzX2F6UnFJUzBpSE5YNEZIRk96eUwwdjE4RXJMdjNyZDBoalpNUnJFIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJlbnRpdGxlbWVudHMiOlt7Im5hbWUiOiJGQVNUIiwiY3JpZHMiOiIxNjg3OTczMiJ9XSwic3ViIjoiNzc5MDEzNTIiLCJjcmlkIjoiMTY4Nzk3MzIiLCJzdWJfaWQiOiI3NzkwMTM1MiIsInJvbGVzIjpbXSwicGF5bWVudF9hY2NvdW50cyI6eyJhY2NvdW50cyI6IjEwMDAwNDAyMTYsIDEwMDAwNDAyMzgsIDEwMDAwNDA0MDMsIDEwMDAwNDA0MDQsIDEwMDAwMzkzNzIifSwiaXNzIjoiaHR0cHM6Ly9rZXljLnVzcHMuY29tL3JlYWxtcy9VU1BTIiwiY29udHJhY3RzIjp7InBheW1lbnRBY2NvdW50cyI6eyJhY2NvdW50cyI6IjEwMDAwNDAyMTYsIDEwMDAwNDAyMzgsIDEwMDAwNDA0MDMsIDEwMDAwNDA0MDQsIDEwMDAwMzkzNzIifSwicGVybWl0cyI6W3sicGVybWl0TnVtYmVyIjoiOTA0NiIsInBlcm1pdFpJUCI6IjIwMjYwMDg0NiJ9XX0sImF1ZCI6WyJwYXltZW50cyIsInByaWNlcyIsInN1YnNjcmlwdGlvbnMtdHJhY2tpbmciLCJvcmdhbml6YXRpb25zIl0sImF6cCI6IjAxUnpQN3VaNFY1MzN3YlY4VWtjY1BWUmFURlVHdm8wajlIdDRyc2Iyd1dzMGhmQSIsIm1haWxfb3duZXJzIjpbeyJjcmlkIjoiMTY4Nzk3MzIiLCJtaWRzIjoiOTAxNjEzNTUyLCA5MDE2MTM0MzcifV0sInNjb3BlIjoiZG9tZXN0aWMtcHJpY2VzIGFkZHJlc3NlcyBpbnRlcm5hdGlvbmFsLXByaWNlcyBzZXJ2aWNlLXN0YW5kYXJkcyBsb2NhdGlvbnMgc2hpcG1lbnRzIiwiY29tcGFueV9uYW1lIjoiV0lMQkVSIFVOSU9OICAgREJBIEhQQyBNQU5VRkFDVFVSSU5HIiwiZXhwIjoxNzQ1MTE3MjAwLCJpYXQiOjE3NDUwODg0MDAsImp0aSI6ImU5MGQxN2EyLTgyYzgtNDM4NC1iYTEwLWFhYmE4ZTUyMjgyMSJ9.l4byyKP2Gr-2k7bfgNplyMQKJetPtvRd9Qm-rFRyIlX8uR35WKoYLxjDEWCe67DeaSuxiEWAef4JYgpsCasqvZTGJm-A32krJ_dEIUWTA1rpqrkPQunOg663eAFrGOTGyx85accJ_tKef_QeJplQfQIlpLDptojhBYVDW5dJoqKk9P_GPQw5tci-JRrgriEZolOjgXzgFKAltNvgzzbE90705RvG4bUOFE9ssPNqM2nZVzYuLE0N0Hd35w0SVRPzGQwEfi8oUkaxnHN0T8EIfyFNRFpADmK5jS1MatnZX-0H6FYpI7u3zwjlGgZPQU0EoCJwgKxyOAmmfrpumRqHXg",
        "token_type": "Bearer",
        "issued_at": 1745088400109,
        "expires_in": 28799,
        "status": "approved",
        "scope": "domestic-prices addresses international-prices service-standards locations shipments",
        "issuer": "https://keyc.usps.com/realms/USPS",
        "client_id": "01RzP7uZ4V533wbV8UkccPVRaTFUGvo0j9Ht4rsb2wWs0hfA",
        "application_name": "xxxxxxxxxxxxxxxxxxxx",
        "api_products": "[Public Access I]",
        "public_key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF1UjQzS0hRdm44UlYybWplMEczSAptKzI3dnA4ZFh4U25oQTVrSk5VbFVJTmZaYnBtZGR6VWpaV1RRMHU0SWdMaTQ5ZDJlTTR1TVE2NjFNdmNyUE5ZCktvRXBWRkNldzJ5QmhqUjJ0T3A5V0gwODgxQ0x3RWdTY3p6YU4vNDAxQ3BSSXlVTjh1aHVuV29ydzZSdkI2S0oKY1NRaFUyMkVrZ0hpZ3ZJTVpDWGt2dmlaa1ByeWI1cTJIcGtMNjVCcFdBRHBYSklpMGJFaXdFU1RSbm9qdkdZSAp3TXJvOCtwVGdmNEN1ZXdpbTdFaDluNm1nbmZnTWp5MUprcjZVVTUzWU1ZdWRrZzB0NHBUb1QvZWd0MHJjZCtwCjJURk82NG9uU21XRDlyWXh1Q0xwYVcwTG1GVGoyYUpTOUt3TnhHb2pIakV5TjhIVG5zd1lOKzdQNVlLVmxobHUKL1FJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t"
    }
            ', NULL, 'kint', ) (Line: 129)
    Drupal\devel\DevelDumperManager->message('
    {
        "access_token": "eyJraWQiOiJIdWpzX2F6UnFJUzBpSE5YNEZIRk96eUwwdjE4RXJMdjNyZDBoalpNUnJFIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJlbnRpdGxlbWVudHMiOlt7Im5hbWUiOiJGQVNUIiwiY3JpZHMiOiIxNjg3OTczMiJ9XSwic3ViIjoiNzc5MDEzNTIiLCJjcmlkIjoiMTY4Nzk3MzIiLCJzdWJfaWQiOiI3NzkwMTM1MiIsInJvbGVzIjpbXSwicGF5bWVudF9hY2NvdW50cyI6eyJhY2NvdW50cyI6IjEwMDAwNDAyMTYsIDEwMDAwNDAyMzgsIDEwMDAwNDA0MDMsIDEwMDAwNDA0MDQsIDEwMDAwMzkzNzIifSwiaXNzIjoiaHR0cHM6Ly9rZXljLnVzcHMuY29tL3JlYWxtcy9VU1BTIiwiY29udHJhY3RzIjp7InBheW1lbnRBY2NvdW50cyI6eyJhY2NvdW50cyI6IjEwMDAwNDAyMTYsIDEwMDAwNDAyMzgsIDEwMDAwNDA0MDMsIDEwMDAwNDA0MDQsIDEwMDAwMzkzNzIifSwicGVybWl0cyI6W3sicGVybWl0TnVtYmVyIjoiOTA0NiIsInBlcm1pdFpJUCI6IjIwMjYwMDg0NiJ9XX0sImF1ZCI6WyJwYXltZW50cyIsInByaWNlcyIsInN1YnNjcmlwdGlvbnMtdHJhY2tpbmciLCJvcmdhbml6YXRpb25zIl0sImF6cCI6IjAxUnpQN3VaNFY1MzN3YlY4VWtjY1BWUmFURlVHdm8wajlIdDRyc2Iyd1dzMGhmQSIsIm1haWxfb3duZXJzIjpbeyJjcmlkIjoiMTY4Nzk3MzIiLCJtaWRzIjoiOTAxNjEzNTUyLCA5MDE2MTM0MzcifV0sInNjb3BlIjoiZG9tZXN0aWMtcHJpY2VzIGFkZHJlc3NlcyBpbnRlcm5hdGlvbmFsLXByaWNlcyBzZXJ2aWNlLXN0YW5kYXJkcyBsb2NhdGlvbnMgc2hpcG1lbnRzIiwiY29tcGFueV9uYW1lIjoiV0lMQkVSIFVOSU9OICAgREJBIEhQQyBNQU5VRkFDVFVSSU5HIiwiZXhwIjoxNzQ1MTE3MjAwLCJpYXQiOjE3NDUwODg0MDAsImp0aSI6ImU5MGQxN2EyLTgyYzgtNDM4NC1iYTEwLWFhYmE4ZTUyMjgyMSJ9.l4byyKP2Gr-2k7bfgNplyMQKJetPtvRd9Qm-rFRyIlX8uR35WKoYLxjDEWCe67DeaSuxiEWAef4JYgpsCasqvZTGJm-A32krJ_dEIUWTA1rpqrkPQunOg663eAFrGOTGyx85accJ_tKef_QeJplQfQIlpLDptojhBYVDW5dJoqKk9P_GPQw5tci-JRrgriEZolOjgXzgFKAltNvgzzbE90705RvG4bUOFE9ssPNqM2nZVzYuLE0N0Hd35w0SVRPzGQwEfi8oUkaxnHN0T8EIfyFNRFpADmK5jS1MatnZX-0H6FYpI7u3zwjlGgZPQU0EoCJwgKxyOAmmfrpumRqHXg",
        "token_type": "Bearer",
        "issued_at": 1745088400109,
        "expires_in": 28799,
        "status": "approved",
        "scope": "domestic-prices addresses international-prices service-standards locations shipments",
        "issuer": "https://keyc.usps.com/realms/USPS",
        "client_id": "01RzP7uZ4V533wbV8UkccPVRaTFUGvo0j9Ht4rsb2wWs0hfA",
        "application_name": "xxxxxxxxxxxxxxxxxxxx",
        "api_products": "[Public Access I]",
        "public_key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF1UjQzS0hRdm44UlYybWplMEczSAptKzI3dnA4ZFh4U25oQTVrSk5VbFVJTmZaYnBtZGR6VWpaV1RRMHU0SWdMaTQ5ZDJlTTR1TVE2NjFNdmNyUE5ZCktvRXBWRkNldzJ5QmhqUjJ0T3A5V0gwODgxQ0x3RWdTY3p6YU4vNDAxQ3BSSXlVTjh1aHVuV29ydzZSdkI2S0oKY1NRaFUyMkVrZ0hpZ3ZJTVpDWGt2dmlaa1ByeWI1cTJIcGtMNjVCcFdBRHBYSklpMGJFaXdFU1RSbm9qdkdZSAp3TXJvOCtwVGdmNEN1ZXdpbTdFaDluNm1nbmZnTWp5MUprcjZVVTUzWU1ZdWRrZzB0NHBUb1QvZWd0MHJjZCtwCjJURk82NG9uU21XRDlyWXh1Q0xwYVcwTG1GVGoyYUpTOUt3TnhHb2pIakV5TjhIVG5zd1lOKzdQNVlLVmxobHUKL1FJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t"
    }
            ', NULL, 'status', 'kint') (Line: 453)
    ksm('
    {
        "access_token": "eyJraWQiOiJIdWpzX2F6UnFJUzBpSE5YNEZIRk96eUwwdjE4RXJMdjNyZDBoalpNUnJFIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJlbnRpdGxlbWVudHMiOlt7Im5hbWUiOiJGQVNUIiwiY3JpZHMiOiIxNjg3OTczMiJ9XSwic3ViIjoiNzc5MDEzNTIiLCJjcmlkIjoiMTY4Nzk3MzIiLCJzdWJfaWQiOiI3NzkwMTM1MiIsInJvbGVzIjpbXSwicGF5bWVudF9hY2NvdW50cyI6eyJhY2NvdW50cyI6IjEwMDAwNDAyMTYsIDEwMDAwNDAyMzgsIDEwMDAwNDA0MDMsIDEwMDAwNDA0MDQsIDEwMDAwMzkzNzIifSwiaXNzIjoiaHR0cHM6Ly9rZXljLnVzcHMuY29tL3JlYWxtcy9VU1BTIiwiY29udHJhY3RzIjp7InBheW1lbnRBY2NvdW50cyI6eyJhY2NvdW50cyI6IjEwMDAwNDAyMTYsIDEwMDAwNDAyMzgsIDEwMDAwNDA0MDMsIDEwMDAwNDA0MDQsIDEwMDAwMzkzNzIifSwicGVybWl0cyI6W3sicGVybWl0TnVtYmVyIjoiOTA0NiIsInBlcm1pdFpJUCI6IjIwMjYwMDg0NiJ9XX0sImF1ZCI6WyJwYXltZW50cyIsInByaWNlcyIsInN1YnNjcmlwdGlvbnMtdHJhY2tpbmciLCJvcmdhbml6YXRpb25zIl0sImF6cCI6IjAxUnpQN3VaNFY1MzN3YlY4VWtjY1BWUmFURlVHdm8wajlIdDRyc2Iyd1dzMGhmQSIsIm1haWxfb3duZXJzIjpbeyJjcmlkIjoiMTY4Nzk3MzIiLCJtaWRzIjoiOTAxNjEzNTUyLCA5MDE2MTM0MzcifV0sInNjb3BlIjoiZG9tZXN0aWMtcHJpY2VzIGFkZHJlc3NlcyBpbnRlcm5hdGlvbmFsLXByaWNlcyBzZXJ2aWNlLXN0YW5kYXJkcyBsb2NhdGlvbnMgc2hpcG1lbnRzIiwiY29tcGFueV9uYW1lIjoiV0lMQkVSIFVOSU9OICAgREJBIEhQQyBNQU5VRkFDVFVSSU5HIiwiZXhwIjoxNzQ1MTE3MjAwLCJpYXQiOjE3NDUwODg0MDAsImp0aSI6ImU5MGQxN2EyLTgyYzgtNDM4NC1iYTEwLWFhYmE4ZTUyMjgyMSJ9.l4byyKP2Gr-2k7bfgNplyMQKJetPtvRd9Qm-rFRyIlX8uR35WKoYLxjDEWCe67DeaSuxiEWAef4JYgpsCasqvZTGJm-A32krJ_dEIUWTA1rpqrkPQunOg663eAFrGOTGyx85accJ_tKef_QeJplQfQIlpLDptojhBYVDW5dJoqKk9P_GPQw5tci-JRrgriEZolOjgXzgFKAltNvgzzbE90705RvG4bUOFE9ssPNqM2nZVzYuLE0N0Hd35w0SVRPzGQwEfi8oUkaxnHN0T8EIfyFNRFpADmK5jS1MatnZX-0H6FYpI7u3zwjlGgZPQU0EoCJwgKxyOAmmfrpumRqHXg",
        "token_type": "Bearer",
        "issued_at": 1745088400109,
        "expires_in": 28799,
        "status": "approved",
        "scope": "domestic-prices addresses international-prices service-standards locations shipments",
        "issuer": "https://keyc.usps.com/realms/USPS",
        "client_id": "01RzP7uZ4V533wbV8UkccPVRaTFUGvo0j9Ht4rsb2wWs0hfA",
        "application_name": "xxxxxxxxxxxxxxxxxx",
        "api_products": "[Public Access I]",
        "public_key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF1UjQzS0hRdm44UlYybWplMEczSAptKzI3dnA4ZFh4U25oQTVrSk5VbFVJTmZaYnBtZGR6VWpaV1RRMHU0SWdMaTQ5ZDJlTTR1TVE2NjFNdmNyUE5ZCktvRXBWRkNldzJ5QmhqUjJ0T3A5V0gwODgxQ0x3RWdTY3p6YU4vNDAxQ3BSSXlVTjh1aHVuV29ydzZSdkI2S0oKY1NRaFUyMkVrZ0hpZ3ZJTVpDWGt2dmlaa1ByeWI1cTJIcGtMNjVCcFdBRHBYSklpMGJFaXdFU1RSbm9qdkdZSAp3TXJvOCtwVGdmNEN1ZXdpbTdFaDluNm1nbmZnTWp5MUprcjZVVTUzWU1ZdWRrZzB0NHBUb1QvZWd0MHJjZCtwCjJURk82NG9uU21XRDlyWXh1Q0xwYVcwTG1GVGoyYUpTOUt3TnhHb2pIakV5TjhIVG5zd1lOKzdQNVlLVmxobHUKL1FJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t"
    }
            ') (Line: 734)
    League\OAuth2\Client\Provider\AbstractProvider->getParsedResponse(Object) (Line: 646)
    League\OAuth2\Client\Provider\AbstractProvider->getAccessToken(Object, Array) (Line: 35)
    Drupal\oauth2_client\Plugin\Oauth2GrantType\ClientCredentials->getAccessToken(Object) (Line: 297)
    Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginBase->getAccessToken(Object) (Line: 44)
    Drupal\oauth2_client\Service\Oauth2ClientService->getAccessToken('authcode', Object) (Line: 330)

    But as what started this, nothing is found in the Drupal message log and no oauth2_client filter is present

    I will do some work on authorization code grant next trying to call this / the token before expiration but right now authorization_code grant throws

    <code>{
      "fault": {
        "faultstring": "OASValidation OpenAPI-Spec-Validation-OAuth2 with resource \"oas://oauth2-3_0_2.yaml\": failed with reason: \"[ERROR - GET operation not allowed on path '/token'.: []]\"",
        "detail": {
          "errorcode": "steps.oasvalidation.Failed"
        }
      }
    }

    So apparently it always has worked but no message of getting the token was ever placed anywhere to inform of success as you stated is supposed to occur

  • 🇺🇸United States fathershawn New York

    So apparently it always has worked but no message of getting the token was ever placed anywhere to inform of success as you stated is supposed to occur

    I have not been able to pull and print it from token storage however

    The message display is a conditional in the StateTokenStorage trait. I have just re-verified that it is functioning correctly.

      /**
       * Stores access tokens obtained by the client.
       *
       * @param \League\OAuth2\Client\Token\AccessTokenInterface $accessToken
       *   The token to store.
       */
      public function storeAccessToken(AccessTokenInterface $accessToken): void {
        $this->state->set('oauth2_client_access_token-' . $this->getId(), $accessToken);
        if ($this->displaySuccessMessage()) {
          $this->messenger->addStatus(
            $this->t('OAuth token stored.')
          );
        }
      }
    

    I don't find any cloud based services for testing the client credentials flow, but you can configure and authorization code plugin to intereact with https://docs.wiremock.io/security/oauth2-mock#using-with-your-app

  • 🇺🇸United States bobburns

    Thank you but following your #48 the last thing you say is

    "If JSON is returned it is parsed and turned into an AccessToken object. Otherwise errors are thrown."

    But since there is no error nor success message and I cannot retrieve it, it must not be getting either parsed or stored.

    Is there a way to call and print to the screen what is stored???

    It appears StateTokenStorage is empty

  • 🇺🇸United States fathershawn New York

    The snippet of code provided in #75 is from the StateTokenStorage which itself is provided to reduce the code that developers need to implement in their plugins.

    If the StateTokenStorage file is empty of code then something is wrong with your code base. If you mean that nothing has been stored in the State system see my next paragraph.

    The interface requires that you implement public function storeAccessToken(AccessTokenInterface $accessToken) but while you are developing you don't have to store anything. You could simply print info from the token object to a message in your implementation of this method and not use the trait.

  • 🇺🇸United States bobburns

    No e of this is my code.

    There is no success message nor error, so what proof is the token is stored?

    I used

    $access_token = Drupal::service('oauth2_client.service')->getAccessToken($client_id);
    $token = $access_token->getToken();
    

    To try to inspect or print $token

  • 🇺🇸United States fathershawn New York

    The storage is implemented by your own plugin class as the kind of storage varies by use case. My suggestion in #78 was to implement the storage methods but within them use a dpm to see the values rather than store them so you could see what was returned.

    Alternatively you can use the provided storage trait and put a dpm() or ksm() call in the trait.

  • 🇩🇪Germany tobiasb Berlin

    Other examples https://git.drupalcode.org/project/openculturas/-/tree/2.5.x/profile/mod...

    Use https://api.usps.com/oauth2/v3/authorize for authorization_uri.

Production build 0.71.5 2024