kamermans/guzzle-oauth2-subscriber

Need to send password and username as POST fields

Closed this issue · 7 comments

Hi,

for the API from DHL I need to send the username and password as POST fields (application/x-www-form-urlencoded). The grant_type is still client_credentials. How can I add these two fields in my request? Thanks for your help!

<?php
require_once('vendor/autoload.php');

use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use kamermans\OAuth2\GrantType\ClientCredentials;
use kamermans\OAuth2\OAuth2Middleware;
use kamermans\OAuth2\Signer\ClientCredentials\PostFormData;

try {

	$reauth_client = new Client(['base_uri' => 'https://api-eu.dhl.com/post/de/shipping/im/v1/user', 'debug' => false]);
	$reauth_config = [
   	 	"grant_type" => "client_credentials",
   	 	"client_id" => "<MY CLIENT ID>",
   	 	"client_secret" => "<MY SECRET>",
		"username" => "<MY USERNAME>",
		"password" => "<MY PASSWORD>"
	];
	
	$grant_type = new ClientCredentials($reauth_client, $reauth_config);
	$oauth = new OAuth2Middleware($grant_type);
	$oauth->setClientCredentialsSigner(new PostFormData());
	$stack = HandlerStack::create();
	$stack->push($oauth);
	
	// This is the normal Guzzle client that you use in your application
	$client = new GuzzleHttp\Client(['handler' => $stack, 'auth' => 'oauth']);
	$response = $client->get('https://api-eu.dhl.com/post/de/shipping/im/v1/user/profile');

	echo "Status: ".$response->getStatusCode()."\n";
	

I think you'd have to create a new Signer based on kamermans\OAuth2\Signer\ClientCredentials\PostFormData:

<?php

// DHLFormData.php
// Custom form data signer for DHL

use kamermans\OAuth2\Utils\Helper;
use kamermans\OAuth2\Signer\ClientCredentials\SignerInterface;
use GuzzleHttp\Post\PostBodyInterface;

class DHLFormData implements SignerInterface
{

    private $username;
    private $password;

    public function __construct($username, $password)
    {
        $this->username = $username;
        $this->password = $password;
    }

    public function sign($request, $clientId, $clientSecret)
    {
        if (Helper::guzzleIs('>=', 6)) {
            if ($request->getHeaderLine('Content-Type') != 'application/x-www-form-urlencoded') {
                throw new \RuntimeException('Unable to set fields in request body');
            }

            parse_str($request->getBody(), $data);
            $data['client_id'] = $clientId;
            $data['client_secret'] = $clientSecret;
            $data['username'] = $this->username;
            $data['password'] = $this->password;

            $body_stream = Helper::streamFor(http_build_query($data, '', '&'));
            return $request->withBody($body_stream);
        }

        $body = $request->getBody();

        if (!($body instanceof PostBodyInterface)) {
            throw new \RuntimeException('Unable to set fields in request body');
        }

        $body->setField('client_id', $clientId);
        $body->setField('client_secret', $clientSecret);
        $body->setField('username', $this->username);
        $body->setField('password', $this->password);

        return $request;
    }
}

And then use it like this:

<?php
require_once('vendor/autoload.php');

use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use kamermans\OAuth2\GrantType\ClientCredentials;
use kamermans\OAuth2\OAuth2Middleware;

try {

	$reauth_client = new Client(['base_uri' => 'https://api-eu.dhl.com/post/de/shipping/im/v1/user', 'debug' => false]);
	$reauth_config = [
   	 	"grant_type" => "client_credentials",
   	 	"client_id" => "<MY CLIENT ID>",
   	 	"client_secret" => "<MY SECRET>"
	];

	$signer = new DHLFormData("<MY USERNAME>", "<MY PASSWORD>");
	
	$grant_type = new ClientCredentials($reauth_client, $reauth_config);
	$oauth = new OAuth2Middleware($grant_type);
	$oauth->setClientCredentialsSigner($signer);
	$stack = HandlerStack::create();
	$stack->push($oauth);
	
	// This is the normal Guzzle client that you use in your application
	$client = new GuzzleHttp\Client(['handler' => $stack, 'auth' => 'oauth']);
	$response = $client->get('https://api-eu.dhl.com/post/de/shipping/im/v1/user/profile');

	echo "Status: ".$response->getStatusCode()."\n";

Thank you very much. I seems that DHL does not respond with "access_token". They call it "userToken". I think this can not handle guzzle-oauth2-subscriber without some changes, right? Can you help me a second time?

{ "userToken": "ABCDEFG...=", "walletBalance": 1000000, "showTermsAndConditions": true, "infoMessage": "", "token_type": "BearerToken", "expires_in": 86400, "issued_at": "Thu, 02 May 2024 20:15:21 GMT", "external_customer_id": "<MY CUSTOMER ID>", "authenticated_user": "<MY USERNAME>" }

Weird, that definitely goes against the OAuth2 spec. Let me see if that's adjustable.

This is similar to GitHub applications, which use username, password and a different field for access token:
https://github.com/kamermans/guzzle-oauth2-subscriber/blob/master/src/GrantType/Specific/GithubApplication.php#L104

Ok, how about this :)

DHLAuth.php

<?php

use GuzzleHttp\ClientInterface;
use kamermans\OAuth2\Utils\Collection;
use GuzzleHttp\Message\Response;
use GuzzleHttp\Stream\Stream;
use kamermans\OAuth2\Utils\Helper;
use kamermans\OAuth2\GrantType\GrantTypeInterface;
use kamermans\OAuth2\Signer\ClientCredentials\SignerInterface;

/**
 * DHL Authentication
 */
class DHLAuth implements GrantTypeInterface
{
    /**
     * The token endpoint client.
     *
     * @var ClientInterface
     */
    private $client;

    /**
     * Configuration settings.
     *
     * @var Collection
     */
    private $config;

    /**
     * @param ClientInterface $client
     * @param array           $config
     */
    public function __construct(ClientInterface $client, array $config)
    {
        $this->client = $client;
        $this->config = Collection::fromConfig(
            $config,
            // Defaults
            [],
            // Required
            [
                'client_id',
                'client_secret',
                'username',
                'password',
            ]
        );
    }

    public function getRawData(SignerInterface $clientCredentialsSigner, $refreshToken = null)
    {
        if (Helper::guzzleIs('>=', 6)) {
            $request = (new \GuzzleHttp\Psr7\Request('POST', ''))
                ->withBody($this->getPostBody())
                ->withHeader('Content-Type', 'application/x-www-form-urlencoded');
        } else {
            $request = $this->client->createRequest('POST', null);
            $request->setBody($this->getPostBody());
        }

        $response = $this->client->send($request);

        // Restructure some fields from the GitHub response
        /* Example Response:
        {
            "userToken": "ABCDEFG...=",
            "walletBalance": 1000000,
            "showTermsAndConditions": true,
            "infoMessage": "",
            "token_type": "BearerToken",
            "expires_in": 86400,
            "issued_at": "Thu, 02 May 2024 20:15:21 GMT",
            "external_customer_id": "<MY CUSTOMER ID>",
            "authenticated_user": "<MY USERNAME>"
        }
        */

        $data = json_decode($response->getBody(), true);
        $data['access_token'] = $data['userToken'];
        unset($data['userToken']);

        return $data;
    }

    protected function getPostBody()
    {
        $postBody = [
            'client_id'     => $this->config['client_id'],
            'client_secret' => $this->config['client_secret'],
            'username'      => $this->config['username'],
            'password'      => $this->config['password'],
        ];

        $postBody = http_build_query($postBody, '', '&');

        return Helper::guzzleIs('<', 6) ? Stream::factory($postBody) : Helper::streamFor($postBody);
    }
}

Example:

<?php
require_once('vendor/autoload.php');

use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use kamermans\OAuth2\GrantType\ClientCredentials;
use kamermans\OAuth2\OAuth2Middleware;
use kamermans\OAuth2\Signer\ClientCredentials\PostFormData;

try {

    $reauth_client = new Client(['base_uri' => 'https://api-eu.dhl.com/post/de/shipping/im/v1/user', 'debug' => false]);
    $reauth_config = [
        "grant_type" => "client_credentials",
        "client_id" => "<MY CLIENT ID>",
        "client_secret" => "<MY SECRET>",
        "username" => "<MY USERNAME>",
        "password" => "<MY PASSWORD>",
    ];

    $grant_type = new DHLAuth($reauth_client, $reauth_config);
    $oauth = new OAuth2Middleware($grant_type);
    $stack = HandlerStack::create();
    $stack->push($oauth);

    // This is the normal Guzzle client that you use in your application
    $client = new GuzzleHttp\Client(['handler' => $stack, 'auth' => 'oauth']);
    $response = $client->get('https://api-eu.dhl.com/post/de/shipping/im/v1/user/profile');

    echo "Status: ".$response->getStatusCode()."\n";

Note that this is completely untested and it would be up to you to maintain it, but I think it does what you want, and if it does work fine, I can add it to this repo as a specific grant type like GithubApplication.

Thanks you very much. It works if you add

}
catch (Exception $e) {
	echo "Caught exception: ".$e->getMessage()."\n";
}

at the end (I forget this too). You have to add "grant_type" as another required field and 'grant_type' => $this->config['client_credentials'], in the $postBody Array.

In DHLAuth.php you have to replace the word "Github" with "DHL". The result should be // Restructure some fields from the DHL response

Thanks for the info and I'm glad it's working for you :)