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 will try pasting all the form code into my form file next.
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???
| Comment | File | Size | Author |
|---|---|---|---|
| #66 | bad-credentials.png | 81.19 KB | fathershawn |
| #66 | usps_api_example_3.zip | 5.58 KB | fathershawn |
| #38 | usps_api_example_2.zip | 4.71 KB | fathershawn |
| #36 | usps_api_example.zip | 4.79 KB | fathershawn |
| #28 | timestamp_proof.jpg | 34.46 KB | bobburns |
Comments
Comment #2
bobburns commentedComment #3
fathershawnCan you be more specific about what credential data you need beyond client id and secret?
Comment #4
bobburns commentedA "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
Comment #5
fathershawnYou 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... ?
Comment #6
bobburns commentedYes 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
I do have the key module installed and it is holding stripe values currently
There is nothing in admin/config/Oauth2-client and says no clients are configured yet
There is no button for a new client or to get a test token etc
Comment #7
fathershawnIf you don't see your plugin listed at
/admin/config/system/oauth2-clientthen 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/Oauth2ClientAre you using Attribute or Annotation for metadata?
Comment #8
bobburns commentedThank 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/xxxxxxxxxxxx/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 worked, however still it too is impossible to delete.
Even if I delete the entire Oauth2client directory from the plugin directory still it complains looking for the old files.
Putting them back will let it work again
But this is still a "BUG" because of the " = " sign issue and the 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
Attempting to fix the @Oauth2Client( code throws
ParseError: syntax error, unexpected token "class" in Composer\Autoload\{closure}() (line 38 of modules/xxxxxxxxxxxxx/src/Plugin/Oauth2Client/AuthCodeExample.php).
However using #[Oauth2Client( . . . the code . . . . )] (as from the Instagram example) works without throwing the class error.
The documentation and examples should not have the code which uses the " = " sign and @Oauth2Client(
AND . . . how does one delete the clients ??
In any event making changes to the file manually results in
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).
An attempt to use the clone feature in the pull down throws
TypeError: Cannot assign null to property Drupal\oauth2_client\Entity\Oauth2Client::$id of type string in Drupal\Core\Entity\EntityBase->createDuplicate() (line 373 of core/lib/Drupal/Core/Entity/EntityBase.php).
Need a big can of RAID here . . .
Comment #9
fathershawnI 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:
\Drupal\oauth2_client\Plugin\Oauth2Client\Oauth2ClientPluginBase\Drupal\oauth2_client\Annotation\Oauth2Clientor\Drupal\oauth2_client\Attribute\Oauth2Client.Comment #10
bobburns commentedOK - 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 see the difference now between attribute and annotation. The examples are attribute, and the Instagram example is annotation.
I have edited the client form to include the CRID and MID.
It appears the client form only catches an error on failure to receive a token, so what about an optional "success" catch, so is there a log system as to token requests to know if it is working being sent tokens??
So does editing the file after discovery do NOTHING?
my "Code.php"file is below
<?php
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 {
/*
* 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;
}
Comment #11
fathershawnI 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 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.
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;
PHP commonly uses Guzzle for such requests and Drupal provides a factory for Guzzle clients,
\Drupal\Core\Http\ClientFactorywhich has a means for setting these headers.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.
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
Comment #12
bobburns commentedThank 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 application name registered.
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 USPS Github page says:
"You will need to add an app in the API Developer Portal to get Consumer Key and Consumer Secret values. You will need a valid customer registration ID (CRID) and mailer ID (MID) in order to get an access token. "
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 is why 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.
So, no after uninstall and re-install and rediscover neither form gets a token registered in the log messages
There is another file which handles CURL calls against the USPS API which is where I had planned to use the $token call, but if I cannot get and store a token for access , that is moot.
I think this has to do with how the Oauth2Client code and Plugin handles json application payloads.
Is there a line of code I need to add or can add to the Plugin???
Comment #13
fathershawnThese 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 the API endpoints listed at https://developers.usps.com/apis that you plan to use. Each of these shows a distinct and different value for scope. You would 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. So let's pick one, pick either authorization flow listed for that one endpoint, and let's work on that one and expand from there.
Comment #14
bobburns commentedAre 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 not show up changes in the config page for me - only a fresh file and flushing caches 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 I know will catch the response errors to the log
Comment #15
fathershawnIt's in the trait that you have included in the plugin already.
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:
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.
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::getDefinitionsComment #16
bobburns commentedI 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 be going on with JSON or I am not being sent a token payload at all. I will contact USPS support to see if there is a recommended way to test other than CURL while I set up to try the CURL examples anyway
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
Comment #17
fathershawnThat'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.
Comment #18
bobburns commentedThis 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 credential USPS example curl call using my config menu and a temporary postfield of client credentials grant type in the file that makes the API calls which should throw a response to the log
Then I will try the Simple oauth module - then I will contact USPS with questions.
When I was working on a oauth connect project with Stripe Connect I was able to throw responses to the browser which let troubleshooting find the problem in the initial request causing the failure of the return token.
Is there any way to change code in this module to temporarily throw all responses to the browser???
Comment #19
fathershawnYes, 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.
Comment #20
bobburns commentedThank 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.
Comment #21
fathershawnThe 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\GenericProviderLook at
\League\OAuth2\Client\Provider\GenericProvider::getAccessTokenMethodwhich if there is no override value created above calls\League\OAuth2\Client\Provider\AbstractProvider::getAccessTokenMethodwhich 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
::getProviderin your plugin and changing how the provider is instantiated.Comment #22
bobburns commentedOK, 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, you say this can be done in the plugin itself - I can see calling the files you mention in a "use" statement
I dont have \League\OAuth2\Client I have /league/oauth2-client/src/Provider in the vendor directory. The concern I have is the OAuth2\Client versus oauth2-client and the latest php library at https://github.com/thephpleague/oauth2-client/tree/master/src/Provider which I can see looks corrected by namespace.
So how are you saying to do this in the plugin itself ??? I am new to the world of plugins
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
Comment #23
fathershawnComment #24
bobburns commentedOK, 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. 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
Comment #25
fathershawnI'm happy to keep chatting. This felt like a conclusion to me:
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
And your reply is
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.
Comment #26
bobburns commentedClient 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
That piece of code I posted was my intention to program a switch case behind that menu selection but then you said it could be done by override in the Plugin. While asking how, I then found the issue where you wrote an override on a client credentials flow that I previously linked. I have never been talking about authorization flow, I have been saying I cannot go there until I get the client credentials token request working first. Then using that token I can gain access to the API in an access grant authorization flow.
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.""
Yet it appears the "authorization code" grant type does send a "GET" because that is how the Plugin can throw the GET not allowed error into the browser on a call to /token endpoint and /authorize. It must be noted all of the USPS call are POST code requests.
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 as the USPS Curl example shows.
I do not know what you are talking about getting Oauth2 working with a reliable service. It already is working. Postman works perfectly with my credentials manually to USPS.
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.
Comment #27
fathershawnAll 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.
Comment #28
bobburns commentedThe form is the default and this is the plugin which fails
<?php
declare(strict_types=1);
namespace Drupal\xxxxxxxxxxxxxxx\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
Comment #29
fathershawnI think I found the disconnect! The Oauth2 standard for client credentials states:
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:
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.
Comment #30
fathershawnThe
\League\OAuth2\Client\OptionProvider\PostAuthOptionProviderenforces 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 atsrc/OAuth2/Client/OptionProvider/UspsClientCredentialsOptionProvider.php, and be sure to set the namespace to the right value for your module.Next, add an override in your plugin class. First add the proper use statement for your new options provider:
Then override the
getProvidermethod.Comment #31
bobburns commentedThank you for this; however, it does not like
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
Comment #32
fathershawnDo you have database logging enabled? If not do you have access to PHP error logs? Can you post any errors shown in your logs?
Comment #33
bobburns commentedEnabled logging and rotated logs manually to clear the junk.
Initially the plugin was missing a use statement for AbstractProvider
I added both
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" ???
Comment #34
fathershawnOwnerCredentials 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_requestis 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:
json:
So try adding a scope value or values to your plugin annotation.
Comment #35
bobburns commentedThat 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
Comment #36
fathershawnYour metadata is not correct in #24.
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.
Comment #37
bobburns commentedMy #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 see where it is done in reverse sort of in https://www.drupal.org/project/oauth2_client/issues/3384244
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.
Comment #38
fathershawnYou 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.
Comment #39
fathershawnI 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
Comment #40
bobburns commentedYes 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.
Even the "Auth code" comment above the @0auth2Client code might cause it as well as the ID or name, so first I will strip everything but what is shown in the example from USPS.
I will report back later.
Comment #41
fathershawnSounds 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-646Comment #42
bobburns commentedIn 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.
Comment #43
fathershawnThat 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:
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?
Comment #44
bobburns commentedNo, 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
Because in AbstractProvider at line 634 it is defined as
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
So if I change it to
and
in the form itself to
'#default_value' => $credentials['clientSecret'] ?? '',and'#default_value' => $credentials['clientId'] ?? '',will that interfere with other place in the module code??? I do not see it called anywhere else in the module, and this variable name will match the library
Comment #45
fathershawnYou do not need to rewrite the module code. You do not really need to verify that the upstream package
AbstractProvideractually 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
Comment #46
fathershawnAlso, we just launched more extensive documentation at https://project.pages.drupalcode.org/oauth2_client/
Comment #47
bobburns commentedThank 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???
Comment #48
fathershawnThat'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.
In addition to the automated tests, I have five different client credential plugins for this module running on sites that I maintain.
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 comes 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:
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
$oauth2ClientServiceis an instance ofOauth2ClientService. This is not a resource owner grant so only the plugin id is needed as a parameter:Here's the method being called:
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.Your token is using client credentials grant so we look to
The ClientCredentialsOptionProvider adds scope, we'll come back to it when it actually gets used.
The first line above calls the
::getProvidermethod in the plugin. The one we are using depends on the base class so what's called isHere 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
GenericProviderfrom the league/oauth2-client library.GenericProvideruses the parentAbstractProvider::getAccessTokenHere your data gets prepared. This helper method gets called:
The default method is POST
The url is the token url that was set from your plugin definition.
The options are set in this case by the
ClientCredentialsOptionProvider::getAccessTokenOptionswhich adds scope to the parameters, and calls\League\OAuth2\Client\OptionProvider\PostAuthOptionProvider::getAccessTokenOptionsWhich sets the content type and encodes the values for the request body.
This is all sent to a request factory and sent back from
getAccessTokenRequestas a request object.We are now to
::getParsedResponsein the code above which calls::getResponsewhich uses a Guzzle client to send the request.If JSON is returned it is parsed and turned into an
AccessTokenobject. Otherwise errors are thrown.Comment #49
bobburns commentedYes, 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.
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 string which will fail
Comment #50
fathershawnYour credentials are stored based on from submission. See
\Drupal\oauth2_client\Form\Oauth2ClientForm::submitFormAnd then
\Drupal\oauth2_client\Service\CredentialProvider::getCredentialsis used to retrieve those values.This service is called in the base plugin getters for these values. For example:
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.
Comment #51
bobburns commentedAnd 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
Comment #52
fathershawnAll 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.
I previously posted where this method is called in the constructor within
Oauth2ClientPluginBase::getProviderThis gets stored by the constructor of
GenericProviderin theclientIdproperty.I posted the code for
\League\OAuth2\Client\Provider\AbstractProvider::getAccessTokenin #48 which shows that theclientIdproperty is stored into the$paramsarray. I also showed the code in #48 of how this value gets encoded and placed as the body of the request.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.
Comment #53
bobburns commentedI 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...
Comment #54
fathershawnThere's the issue then and it is my code. We have
plugin class:
The method below omits scope and all other body data except your credentials.
urlResourceOwnerDetailsis required by GenericProvider but will be dropped before the request if empty. Add it to yourOauth2Clientplugin class to override behavior inOauth2ClientPluginBaseThis results in the following data prepared for the request in my local testing:
Comment #55
fathershawnComment #56
bobburns commentedThank 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
So right now - even though oauth2_client reinstalls - the entity is not seen. The php log shows
[12-Apr-2025 14:39:44] WARNING: [pool xxxxxxx.com] child 356802 said into stderr: "NOTICE: PHP message: Uncaught PHP Exception Drupal\Component\Plugin\Exception\PluginNotFoundException: "The "oauth2_client" entity type does not exist." at /var/www/xxxxxxxx/public_html/core/lib/Drupal/Core/Entity/EntityTypeManager.php line 142"
Did I put the function in the wrong place?? The plugin runs and enables without error, but the database logger does not show "oauth2_client" any longer either
Comment #57
fathershawnI 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.
Comment #58
bobburns commentedIt appears related to multi lingual config_translation.
It looks like
Could be caused by a module which doesn't provide a base path for a config entity, see for example:
https://www.drupal.org/project/dynamic_layouts/issues/3002255 https://www.drupal.org/project/password_policy/issues/2703717 https://www.drupal.org/project/paragraphs_entity_embed/issues/3014901 https://www.drupal.org/project/search_api/issues/2919676 https://www.drupal.org/project/salesforce/issues/3040022
All of these have patches for the issue
Still and yet somehow on re install no entity is created for oauth2_client
Comment #59
fathershawnThis module does provide the route described in those issues
Oauth2ClientListBuildertriggers 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.Comment #60
fathershawnComment #61
bobburns commentedOK 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
The last communicating Plugin was from posts 33 to 35.
Comment #62
fathershawnPlease add the Devel module to your test site:
https://www.drupal.org/project/devel
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: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.Comment #63
bobburns commentedI 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
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
Comment #64
fathershawnYou 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
Comment #65
bobburns commentedIf I change the class to
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
Comment #66
fathershawnMaking 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:
With this in place, and this plugin (Also uploaded in a zip file as example 3)
I get this message after clicking "Save and request token"
Comment #67
bobburns commentedI duplicated the AbstractProvider.php to AbstractProvider_php and modified the original by dropping in the new ksm code complete function. Nothing 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 message 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 oauth library, but I put the function you provided 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
I installed this patch and it points to this module routing.yml file as the source. https://www.drupal.org/files/issues/2020-05-26/drupal-3016131-23-useless...
Comment #68
fathershawnIf 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?
Comment #69
bobburns commentedMy 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.
I will also ask USPS if a contractors developers id and secret is available. They said if my id and secret gets compromised they will issue another
There is a Drupal group - that meets regularly, but I do not thing they would be sophisticated enough to handle this - it is mainly newbies and one or two regulars who are not coders, but maintenance types
Comment #70
bobburns commentedLooking 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
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
Comment #71
fathershawnWhat 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 for what I understand your development situation to be.
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.
Comment #72
bobburns commentedI 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
Comment #73
fathershawnI have different results. In a clean install of Drupal, using
getParsedResponsealso posted in #66I 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 use one of Devel's other functions there such
dpm()instead.Comment #74
bobburns commentedOK 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
which is the code structure from #30 and Drupal\xxxxxxxxxxx\OAuth2\Client\OptionProvider\UspsClientCredentialsOptionProvider
is
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
The straight example edited with no override as
actually works throwing (three identical)
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
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
Comment #75
fathershawnThe message display is a conditional in the
StateTokenStoragetrait. I have just re-verified that it is functioning correctly.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
Comment #76
bobburns commentedThank 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
Comment #77
fathershawnThe snippet of code provided in #75 is from the
StateTokenStoragewhich itself is provided to reduce the code that developers need to implement in their plugins.If the
StateTokenStoragefile 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.Comment #78
bobburns commentedNone of this is my code.
There is no success message nor error, so what proof is the token is stored?
I used
To try to inspect or print $token
Comment #79
fathershawnThe 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.
Comment #80
tobiasbOther examples https://git.drupalcode.org/project/openculturas/-/tree/2.5.x/profile/mod...
Use
https://api.usps.com/oauth2/v3/authorizeforauthorization_uri.Comment #81
fathershawn