Code Monkey home page Code Monkey logo

guzzle-oauth2-subscriber's Introduction

Guzzle OAuth 2.0 Subscriber

Tested with Guzzle 4, 5, 6, 7 and PHP 7.1, 7.2, 7.3, 7.4, 8.0 and 8.1.

This is an OAuth 2.0 client for Guzzle which aims to be 100% compatible with Guzzle 4, 5, 6, 7 and all future versions within a single package. Although I love Guzzle, its interfaces keep changing, causing massive breaking changes every 12 months or so, so I have created this package to help reduce the dependency hell that most third-party Guzzle dependencies bring with them. I wrote the official Guzzle OAuth 2.0 plugin which is still on the oauth2 branch, over at the official Guzzle repo, but I see that they have dropped support for Guzzle < v6 on master, which prompted me to split this back off to a separate package.

Features

  • Acquires access tokens via one of the supported grant types (code, client credentials, user credentials, refresh token). Or you can set an access token yourself.
  • Supports refresh tokens (stores them and uses them to get new access tokens).
  • Handles token expiration (acquires new tokens and retries failed requests).
  • Allows storage and lookup of access tokens via callbacks

Installation

This project can be installed using Composer. Run composer require kamermans/guzzle-oauth2-subscriber or add the following to your composer.json:

    {
        "require": {
            "kamermans/guzzle-oauth2-subscriber": "~1.1"
        }
    }

Usage

This plugin integrates seamlessly with Guzzle, transparently adding authentication to outgoing requests and optionally attempting re-authorization if the access token is no longer valid.

There are multiple grant types available like PasswordCredentials, ClientCredentials and AuthorizationCode.

Guzzle 4 & 5 vs Guzzle 6+

With the Guzzle 6 release, most of the library was refactored or completely rewritten, and as such, the integration of this library is different.

Emitters (Guzzle 4 & 5)

Guzzle 4 & 5 use Event Subscribers, and this library includes OAuth2Subscriber for that purpose:

$oauth = new OAuth2Subscriber($grant_type);

$client = new Client([
    'auth' => 'oauth',
]);

$client->getEmitter()->attach($oauth);

Middleware (Guzzle 6+)

Starting with Guzzle 6, Middleware is used to integrate OAuth, and this library includes OAuth2Middleware for that purpose:

$oauth = new OAuth2Middleware($grant_type);

$stack = HandlerStack::create();
$stack->push($oauth);

$client = new Client([
    'auth'     => 'oauth',
    'handler'  => $stack,
]);

Alternatively, you can add the middleware to an existing Guzzle Client:

$oauth = new OAuth2Middleware($grant_type);
$client->getConfig('handler')->push($oauth);

Client Credentials Example

Client credentials are normally used in server-to-server authentication. With this grant type, a client is requesting authorization in its own behalf, so there are only two parties involved. At a minimum, a client_id and client_secret are required, although many services require a scope and other parameters.

Here's an example of the client credentials method in Guzzle 4 and Guzzle 5:

use kamermans\OAuth2\GrantType\ClientCredentials;
use kamermans\OAuth2\OAuth2Subscriber;

// Authorization client - this is used to request OAuth access tokens
$reauth_client = new GuzzleHttp\Client([
    // URL for access_token request
    'base_url' => 'http://some_host/access_token_request_url',
]);
$reauth_config = [
    "client_id" => "your client id",
    "client_secret" => "your client secret",
    "scope" => "your scope(s)", // optional
    "state" => time(), // optional
];
$grant_type = new ClientCredentials($reauth_client, $reauth_config);
$oauth = new OAuth2Subscriber($grant_type);

// This is the normal Guzzle client that you use in your application
$client = new GuzzleHttp\Client([
    'auth' => 'oauth',
]);
$client->getEmitter()->attach($oauth);
$response = $client->get('http://somehost/some_secure_url');

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

Here's the same example for Guzzle 6+:

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

// Authorization client - this is used to request OAuth access tokens
$reauth_client = new GuzzleHttp\Client([
    // URL for access_token request
    'base_uri' => 'http://some_host/access_token_request_url',
]);
$reauth_config = [
    "client_id" => "your client id",
    "client_secret" => "your client secret",
    "scope" => "your scope(s)", // optional
    "state" => time(), // optional
];
$grant_type = new ClientCredentials($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('http://somehost/some_secure_url');

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

Authorization Code Example

There is a full example of using the AuthorizationCode grant type with a RefreshToken in the examples/ directory.

Grant Types

The following OAuth grant types are supported directly, and you can always create your own by implementing kamermans\OAuth2\GrantType\GrantTypeInterface:

  • AuthorizationCode
  • ClientCredentials
  • PasswordCredentials
  • RefreshToken

Each of these takes a Guzzle client as the first argument. This client is used to obtain or refresh your OAuth access token, out of band from the other requests you are making.

Request Signers

There are two cases where we need to sign an HTTP request: when adding client credentials to a request for a new access token, and when adding an access token to a request.

Client Credentials Signers

When requesting a new access token, we need to send the required credentials to the OAuth 2 server. Adding information to a request is called signing in this library.

There are two client credentials signers included in kamermans\OAuth2\Signer\ClientCredentials:

  • BasicAuth: (default) Sends the credentials to the OAuth 2 server using HTTP Basic Auth in the Authorization header.
  • PostFormData: Sends the credentials to the OAuth 2 server using an HTTP Form Body (Content-Type: application/x-www-form-urlencoded). The Client ID is stored in the field client_id and the Client Secret is stored in client_secret. The field names can be changed by passing arguments to the constructor like this: new PostFormData('MyClientId', 'MySecret'); (which would place the ID and secret into the fields MyClientId and MySecret).
  • Json: Sends the credentials to the OAuth 2 server using a JSON (Content-Type: application/json). The Client ID is stored in the field client_id and the Client Secret is stored in client_secret. The field names can be changed by passing arguments to the constructor like this: new Json('MyClientId', 'MySecret'); (which would place the ID and secret into the fields MyClientId and MySecret).

If the OAuth 2 server you are obtaining an access token from does not support the built-in methods, you can either extend one of the built-in signers, or create your own by implementing kamermans\OAuth2\Signer\ClientCredentials\SignerInterface, for example:

use kamermans\OAuth2\Signer\ClientCredentials\SignerInterface;

class MyCustomAuth implements SignerInterface
{
    public function sign($request, $clientId, $clientSecret)
    {
        if (Helper::guzzleIs('~', 6)) {
            $request = $request->withHeader('x-client-id', $clientId);
            $request = $request->withHeader('x-client-secret', $clientSecret);
            return $request;
        }

        $request->setHeader('x-client-id', $clientId);
        $request->setHeader('x-client-secret', $clientSecret);
        return $request;
    }
}

Access Token Signers

When making a request to a REST endpoint protected by OAuth 2, we need to sign the request by adding the access token to it. This library intercepts your requests, signs them with the current access token, and sends them on their way.

The two most common ways to sign a request are included in kamermans\OAuth2\Signer\AccessToken:

  • BearerAuth: (default) Sends the access token using the HTTP Authorization header.
  • BasicAuth: Alias for BearerAuth. Don't use; exists for backwards compatibility only.
  • QueryString: Sends the access token by appending it to the query string. The default query string field name is access_token, and if that field is already present in the request, it will be overwritten. A different field name can be used by passing it to the constructor like this: new QueryString('MyAccessToken'), where MyAccessToken is the field name.

Note: Use of the QueryString signer is discouraged because your access token is exposed in the URL. Also, you should only connect to OAuth-powered services via HTTPS so your access token is encrypted in flight.

You can create a custom access token signer by implementing kamermans\OAuth2\Signer\AccessToken\SignerInterface.

Access Token Persistence

Note: OAuth Access tokens should be stored somewhere securely and/or encrypted. If an attacker gains access to your access token, they could have unrestricted access to whatever resources and scopes were allowed!

By default, access tokens are not persisted anywhere. There are some built-in mechanisms for caching / persisting tokens (in kamermans\OAuth2\Persistence):

  • NullTokenPersistence (default) Disables persistence
  • FileTokenPersitence Takes the path to a file in which the access token will be saved.
  • DoctrineCacheTokenPersistence Takes a Doctrine\Common\Cache\Cache object and optionally a key name (default: guzzle-oauth2-token) where the access token will be saved.
  • SimpleCacheTokenPersistence Takes a PSR-16 SimpleCache and optionally a key name (default: guzzle-oauth2-token) where the access token will be saved. This allows any PSR-16 compatible cache to be used.
  • Laravel5CacheTokenPersistence Takes an Illuminate\Contracts\Cache\Repository object and optionally a key name (default: guzzle-oauth2-token) where the access token will be saved.
  • ClosureTokenPersistence Allows you to define a token persistence provider by providing closures to handle the persistence functions.

If you want to use your own persistence layer, you should write your own class that implements TokenPersistenceInterface or use the ClosureTokenPersistence provider, which is described at the end of this section.

To enable token persistence, you must use the OAuth2Middleware::setTokenPersistence() or OAuth2Subscriber::setTokenPersistence() method, like this:

use kamermans\OAuth2\Persistence\FileTokenPersistence;

$token_path = '/tmp/access_token.json';
$token_persistence = new FileTokenPersistence($token_path);

$grant_type = new ClientCredentials($reauth_client, $reauth_config);
$oauth = new OAuth2Middleware($grant_type);
$oauth->setTokenPersistence($token_persistence);

Closure-Based Token Persistence

There are plenty of cases where you would like to use your own caching layer to store the OAuth2 data, but there is no adapter included that works with your cache provider. The ClosureTokenPersistence provider makes this case easier by allowing you to define closures that handle the OAuth2 persistence data, as shown in the example below.

// We'll store everything in an array, but you can use any provider you want
$cache = [];
$cache_key = "foo";

// Returns true if the item exists in cache
$exists = function() use (&$cache, $cache_key) {
    return array_key_exists($cache_key, $cache);
};

// Sets the given $value array in cache
$set = function(array $value) use (&$cache, $cache_key) {
    $cache[$cache_key] = $value;
};

// Gets the previously-stored value from cache (or null)
$get = function() use (&$cache, $cache_key, $exists) {
    return $exists()? $cache[$cache_key]: null;
};

// Deletes the previously-stored value from cache (if exists)
$delete = function() use (&$cache, $cache_key, $exists) {
    if ($exists()) {
        unset($cache[$cache_key]);
    }
};

$persistence = new ClosureTokenPersistence($set, $get, $delete, $exists);

Note: The format of the token data is a PHP associative array. You can flatten the array with serialize() or json_encode() or whatever else you want before storing it, but remember to decode it back to an array in get() before returning it! Also, the above example is not very thread-safe, so if you have a high level of concurrency, you will need to find more atomic ways to handle this logic, or at least wrap things with try/catch and handle things gracefully.

Please see the src/Persistence/ directory for more information on persistence.

Manually Setting an Access Token

For a manually-obtained access token, you can use the NullGrantType and set the access token manually as follows:

use kamermans\OAuth2\GrantType\NullGrantType;

$oauth = new OAuth2Middleware(new NullGrantType);
$oauth->setAccessToken([
	// Your access token goes here
    'access_token' => 'abcdefghijklmnop',
	// You can specify 'expires_in` as well, but it doesn't make much sense in this scenario
	// You can also specify 'scope' => 'list of scopes'
]);

Note that if the access token is not set using setAccessToken(), a kamermans\OAuth2\Exception\ReauthorizationException will be thrown since the NullGrantType has no way to get a new access token.

Using Refresh Tokens

Refresh tokens are designed to allow a server to request a new access token on behalf of a user that is not present. For example, if some fictional app Angry Rodents wants to post something to the social media site Grillbook on behalf of the user, John Doe, the Angry Rodents app needs an access token for Grillbook. When John Doe first installs this app, it redirects him to the Grillbook site to authorize the Angry Rodents app to post on his behalf, and the Angry Rodents app receives an access token and a refresh token in the process. Eventually the access token expires, but Angry Rodents cannot use the original method (redirecting the user to ask for permission) every time the token expires, so instead, it sends the refresh token to Grillbook, which returns a new access token (and possibly a new refresh token).

To use refresh tokens, you pass a RefreshToken grant type object as the second argument to OAuth2Middleware or OAuth2Subscriber. Normally refresh tokens are only used in the interactive AuthorizationCode grant type (where the user is present), but it is also possible to use them with the other grant types (this is discouraged in the OAuth 2.0 spec). For example, here we are using a refresh token with the ClientCredentials grant type:

// This grant type is used to get a new Access Token and Refresh Token when
//  no valid Access Token or Refresh Token is available
$grant_type = new ClientCredentials($reauth_client, $reauth_config);

// This grant type is used to get a new Access Token and Refresh Token when
//  only a valid Refresh Token is available
$refresh_grant_type = new RefreshToken($reauth_client, $reauth_config);

// Tell the middleware to use the two grant types
$oauth = new OAuth2Middleware($grant_type, $refresh_grant_type);

When using a refresh token to request a new access token, the server may send a new refresh token in the response. If a new refresh token was sent, it will be saved, otherwise the old refresh token will be retained.

guzzle-oauth2-subscriber's People

Contributors

bobab12 avatar cyberwolf avatar daniellienert avatar davereid avatar fabiang avatar gabgr25 avatar gdbonino avatar kamermans avatar lordelph avatar pawelsuwinski avatar roy-bongers avatar thiakil avatar threesquared avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

guzzle-oauth2-subscriber's Issues

Allow custom headers for OAuth requests

We're using trace IDs via the X-Request-ID header to be able to track user requests across multiple systems.

Unfortunately the ClientCredentials grant type ignores the HTTP headers set on the $client object when guzzle 6 or later is used, and thus the request ID is not passed on: https://github.com/kamermans/guzzle-oauth2-subscriber/blob/v1.0.11/src/GrantType/ClientCredentials.php#L55

It would be nice if it used them in the POST OAuth request sent from getRawData().

How to manually set an access token?

There doesn't seem to be a grant_type for setting the access token manually. I'm a little unclear how to manually set the access type. A simple little example would be appreciated. Thanks

incompatible with guzzlehttp/guzzle 7.7.0 along with guzzlehttp/promises 2.0.0

Please be aware that the current version 1.0.12 does not work with guzzlehttp/promises in version 2.0.0, which can be installed along with guzzlehttp/guzzle in version 7.7.0. Anyhow, version 7.7.0 still allows guzzlehttp/promises in version 1.5.3.

e.g. calling to undefined method rejection_for in \kamermans\OAuth2\OAuth2Middleware::onRejected

private function onRejected(RequestInterface $request, array $options, $handler)
{
    return function ($reason) use ($request, $options) {
        return \GuzzleHttp\Promise\rejection_for($reason);
    };
}

Argument 1 passed to kamermans\OAuth2\OAuth2Middleware::__invoke() must be callable, object given, called in /var/www/html/vendor/kamermans/guzzle-oauth2-subscriber/src/OAuth2Middleware.php on line 32

public function initializeObject()
    {
        $this->client = new Client([
            'handler' => $this->getHandlerStack(),
            'auth' => 'oauth',
            'base_uri' => self::BASE_URI,
            'verify' => false,
        ]);
    }

    protected function getHandlerStack()
    {
        $oAuth2Client = new Client([
            'base_uri' => 'https://baseurl.tld/oauth/token?grant_type=client_credentials',
            'verify' => false
        ]);

        $clientCredentials = new ClientCredentials($oAuth2Client, [
            'client_id' => 'myclientid',
            'client_secret' => 'myclientsecret'
        ]);

        $oauth2Middleware = new OAuth2Middleware($clientCredentials);

        $handlerStack = HandlerStack::create($oauth2Middleware);
        $handlerStack->push($oauth2Middleware);

        return $handlerStack;
    }

   public function getFoo()
    {
        return json_decode(
            $this->client->get('/v1/foo')->getBody()->__toString(),
            true
        );
    }

Throws the following exception:

Argument 1 passed to kamermans\OAuth2\OAuth2Middleware::__invoke() must be callable, object given, called in /var/www/html/vendor/kamermans/guzzle-oauth2-subscriber/src/OAuth2Middleware.php on line 32
composer show

kamermans/guzzle-oauth2-subscriber v1.0.3                OAuth 2.0 client for Guzzle 4, 5 and 6+
psr/http-message                   1.0.1                 Common interface for HTTP messages
psr/log                            1.0.2                 Common interface for logging libraries
guzzlehttp/guzzle                  6.3.0                 Guzzle is a PHP HTTP client library
guzzlehttp/promises                v1.3.1                Guzzle promises library
guzzlehttp/psr7                    1.4.2                 PSR-7 message implementation that also provides common utility methods

Unable to get "Client Scoped Token"

The API I am trying to authenticate with defines this workflow:

If you haven’t already been granted access to the xxx Operations API, you should contact your sales representative and request access. Once this step has been done, you should have a username, password and refresh token for your API account. You will need this information to authenticate to the API.

Authentication Workflow
The authentication workflow comprises of two steps:

Use your refresh token to get a client-scoped access token
Use the client-scoped access token to access resources
For each step, the Authorization header must be present.

In Postman I am able to obtain a "Client-scoped access token" (see graphic)
2019-10-28_20-21-36

However, the documentation here on GitHub does not give any clue as to how to provide the provided static refresh token in the authentication process.

Is there any way to do that?

Seems everything else is working. If I take the "Client-scoped Token" generated by Postman manually add it, I'm able to make subsequent resource requests

License file

Hello,

Can you clarify what is the license for this library by creating a LICENSE.md file?

Thanks.

RFC: flexible field naming

Thanks for this library, it's been really easy to integrate with!

I've used it to authenticate with Salesforce Marketing Cloud's REST API. The only complication was that the field names returned by the authentication endpoint are different from those guzzle-oauth2-subscriber expects (accessToken vs access_token, expiresIn vs expires_in) so I had to subclass ClientCredentials to modify the returned data:

<?php

namespace IACircularEmails\SFMC;

use kamermans\OAuth2\GrantType\ClientCredentials;
use kamermans\OAuth2\Signer\ClientCredentials\SignerInterface;

class SFMCClientCredentials extends ClientCredentials
{
    public function getRawData(SignerInterface $clientCredentialsSigner, $refreshToken = null)
    {
        $data = parent::getRawData($clientCredentialsSigner, $refreshToken);

        $data['access_token'] = $data['accessToken'];
        unset($data['accessToken']);
        $data['expires_in'] = $data['expiresIn'];
        unset($data['expiresIn']);

        return $data;
    }
}

The request field names can be configured when setting up the middleware, without any need for subclassing:

$oauth = new OAuth2Middleware(
                              $grant_type, 
                              null, 
                              new PostFormData('clientId', 'clientSecret'), 
                              new QueryString()
                             );

Would you consider adding mapping to the ClientCredentials class, as HWIOAuthBundle does with paths? An example is:

$reauth_config = [
    "client_id" => "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "client_secret" => "******************************",
    "mappings" => [
        "access_token" => 'accessToken',
        "expires_in" => 'expiresIn',
    ]
];
$grant_type = new ClientCredentials($reauth_client, $reauth_config);

Need to send password and username as POST fields

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";
	

Question: How to use Client Credentials and Refresh Token. Possibly missing Signer?

I'm trying to get an API client to authenticate following your instructions but keep running into issues. I think it may have something to do with a present RefreshToken which I know you've mentioned that this isn't how the RFC defines the appropriate process here but I'm wondering if my issue is different.

I'm trying to consume an API with what I assume is ClientCredentials using a RefreshToken. I can get a proper Access Token using cURL like so:

curl -X POST https://domain.com/API/Login/oauth2 
     -H "Content-Type: application/x-www-form-urlencoded" 
     -H 'Authorization: Bearer XXXRefreshTokenXXX' 
     -d "grant_type=client_credentials&client_id=ClientTest&client_secret=ClientSecret&redirect_uri=https://domain.com/Auth/OAuth2"

Now when I try to recreate that using the Guzzle OAuth2 Subscriber library I can't get it to authenticate. I keep getting a 500 error. The code I'm using is this:

$reauth_config = [
    "client_id" => "ClientTest",
    "client_secret" => "ClientSecret",
    "refresh_token" => "XXXRefreshTokenXXX",
   'redirect_uri' => "https://domain.com/Auth/OAuth2",
];
$reauth_client = new GuzzleHttp\Client([
    // URL for access_token request
    'base_uri' => "https://domain.com/API/Login/oauth2",
]);

$grant_type = new ClientCredentials($reauth_client, $reauth_config);
$refresh_grant_type = new RefreshToken($reauth_client, $reauth_config);
$oauth = new OAuth2Middleware($grant_type, $refresh_grant_type);
$oauth->setTokenPersistence($_token_storage);
$stack = HandlerStack::create();
$stack->push($oauth);

$client = new GuzzleHttp\Client([
    'handler' => $stack,
    'auth'    => 'oauth',
]);

$response = $client->get("https://domain.com/API/Member?MemberType=I&DisplayStart=0&DisplayLength=100");

From what I can tell it's not passing the Authorization Bearer along with the request. I've looked into the Signer portion of the library but I honestly can't suss out where to put that code. Is this just a matter of my noobness or something else?

Get Token Payload

Hi,
I`m using this package and it is working perfectly, in some situations I need to get some information from a token of OAuth2. so these questions come next:

  1. How could I access the token of request?
  2. I want the token payload. is there any way to get it?

Using refresh token grant type for 2-legged OAuth

We are trying to make the middleware work with a 2-legged OAuth flow. The server we are connecting to has already authorized our app, and we've been issued a refresh token (which doesn't seem to expire) that we can use to get new access tokens.

However, there's probably something I'm missing or the middleware is not quite designed for this.

I've made it work in the end, but perhaps there's a better way?

Here's my code:

$config = [
    'client_id' => 'ABC',
    'client_secret' => 'DEF',
    'refresh_token' => 'GHI'
];

$clientCredentialsGrantType = new ClientCredentials($refreshClient, $config);
$refreshTokenGrantType = new RefreshToken($refreshClient, $config);
            
$oauth = new OAuth2Middleware($clientCredentialsGrantType, $refreshTokenGrantType);
$oauth->setAccessToken([
    'refresh_token' => $config['refresh_token'],
    'expires_in' => -1
]);

Here I'm tricking the middleware it has acquired an access token which has expired, so that these conditions are met:

  • Inside OAuth2Handler::requestNewAccessToken I'm learning that a raw token must exist:
if ($this->refreshTokenGrantType && $this->rawToken && $this->rawToken->getRefreshToken()) {
  • Next in OAuth2Handler::getAccessToken:
if ($this->rawToken === null || $this->rawToken->isExpired()) {

For the expiration check to be evaluated to true, I'm setting the expiration to a date in the past via expires_in - it can't be the default 0, since isExpired won't allow it:

public function isExpired()
{
    return $this->expiresAt && $this->expiresAt < time();
}

TokenPersistenceInterface->restoreToken always called with new RawToken

I have a use case (Salesforce REST api) which pulls extra fields from the server's token response and stores it as part of the token.
I store it because the extra info (a URL and an OpenID token) is used by some additional middleware.

Most of it works fine, I have a custom TokenFactory & a custom TokenInterface implementation.

My issue is that the persistence subsystem always tries to restore a RawToken, as restoreToken has a TokenInterface param - but it's only ever called with an argument of new RawToken in the codebase.

My workaround currently is to wrap the persist provider and override restoreToken to supply an instance of my token.

What are your thoughts on this? I'm not quite sure why it has the param to begin with (but I understand that changing it would likely be a breaking change)

Incorrect namespaces in OAuth2Handler

in OAuth2Handler:

public function __construct(
                    GrantType\GrantTypeInterface $grantType,
                    GrantType\GrantTypeInterface $refreshTokenGrantType = null,
                    Signer\ClientCredentials\ClientCredentialsSigner $clientCredentialsSigner = null,
                    Signer\AccessToken\AccessTokenSigner $accessTokenSigner = null
    ) {

namespaces Signer\ClientCredentials\ClientCredentialsSigner and Signer\AccessToken\AccessTokenSigner are incorrect.

As workaround can be used setters.

Help with example of using RefreshToken

It is entirely possible I don't know what I'm doing, but... first, I have a routine that uses password grant type for the initial authentication. Using a custom class for persistence. The following does work, though I'm not clear if this is the best approach:

                     $token_persistence = new CustomTokenPersistence();
		$grant_type = new PasswordCredentials($reauth_client, $reauth_config);
		$oauth = new OAuth2Middleware($grant_type);
		$oauth->setTokenPersistence($token_persistence);
		$stack = HandlerStack::create();
		$stack->push($oauth);

		$grant_type = new RefreshToken($reauth_client, $reauth_config);
		$oauth = new OAuth2Middleware($grant_type);	
		$oauth->setTokenPersistence($token_persistence);
		$stack->push($oauth);

User supplies username and password. The authorization token and the refresh token are both received successfully and stored in my db via the CustomTokenPersistence class. All further client requests work fine. All good.

Come back a few days later, the auth token is now expired. I am having trouble finding the right way to automatically use the saved refresh token to request a new auth token (and new refresh token, because they are one time use in this application). This is the code I'm trying to use:

                     $token_persistence = new CustomTokenPersistence();
		$grant_type = new RefreshToken($reauth_client, $reauth_config);	
		$oauth = new OAuth2Middleware($grant_type);
		$oauth->setTokenPersistence($token_persistence);
		$stack = HandlerStack::create();
		$stack->push($oauth);

But it does not work - throws an exception "unable to request a new access token". It seems like it is not picking up the stored refresh token and doing whatever it needs to do with it. I know I'm missing something, but ... what?

Thank you for any guidance!

Guzzle 7 Uncaught Error: Call to undefined function GuzzleHttp\Psr7\stream_for()

When I attempt to get an access token on the latest version of Guzzle, ^7.3, it errors out giving me the following error:

PHP Fatal error:  Uncaught Error: Call to undefined function GuzzleHttp\Psr7\stream_for() in /path/to/project/vendor/kamermans/guzzle-oauth2-subscriber/src/GrantType/ClientCredentials.php:93
Stack trace:
#0 /path/to/project/vendor/kamermans/guzzle-oauth2-subscriber/src/GrantType/ClientCredentials.php(57): kamermans\OAuth2\GrantType\ClientCredentials->getPostBody()
#1 /path/to/project/vendor/kamermans/guzzle-oauth2-subscriber/src/OAuth2Handler.php(285): kamermans\OAuth2\GrantType\ClientCredentials->getRawData(Object(kamermans\OAuth2\Signer\ClientCredentials\BasicAuth))
#2 /path/to/project/vendor/kamermans/guzzle-oauth2-subscriber/src/OAuth2Handler.php(213): kamermans\OAuth2\OAuth2Handler->requestNewAccessToken()
#3 /path/to/project/public/FileName.php(230): kamermans\OAuth2\OAuth2Handler->getAccessTo in /path/to/project/vendor/kamermans/guzzle-oauth2-subscriber/src/GrantType/ClientCredentials.php on line 93

This is the method I've written, maybe I did something wrong, but dropping the Guzzle version to the latest v6, it works:

protected function grabToken()
{
    $client_id     = env("CLIENT_ID");
    $client_secret = env("CLIENT_SECRET");

    try {
        $reauth_client = new Client(['base_uri' => 'https://URL.com/auth/connect/token',]);

        $reauth_config = [
            'client_id'     => $client_id,
            'client_secret' => $client_secret,
        ];

        $grant_type = new ClientCredentials($reauth_client, $reauth_config);
        $refresh_gt = new RefreshToken($reauth_client, $reauth_config);
        $oauth      = new OAuth2Middleware($grant_type, $refresh_gt);

        error_log(print_r($oauth->getAccessToken(), true)); // This is me checking the token's existence

        return $oauth;
    }
    catch (\Exception $exception) {
        return rest_ensure_response($exception->getMessage());
    }
}

It seems not possible to forward custom Guzzle options to the handler

In guzzle it is possible to add cURL options via the $options and the ['curl'] key. I happens to have to deal with a expired SSL cert by a companies sandbox uri. I think I could just move forward, when I add the curl Option to ignore the ssl verify.

This option is ignored in the getAccessToken() process from this middleware. I tracked it down to the ClientCredentials.php:70 where the send($request) is done without an $options array. I think there should be something to forward curl options down the callstack.

Unable to request new access token but manually set access token works

I am trying to integrate in laravel 6 & Guzzle 6.3. But facing the problem. Although manually set OAuth access token works fine.
Here's my request parameter for obtaining access token -
"grant_type" => "password",
"client_id" => "api-client",
"username" => $username,
"password" => $password,
"broker" => $broker,

I put this in config but seems it always shows the error. Any suggestions?

Fitnesssync oauth uses a redirect to get code - how do I get the code other than manually?

I want to get to the fitnesssync api. It requires oauth tokens and authorization to access data.

When you go to the $auth_url (as created in your authorization_code example) I get a chance to approve/deny access.
When I click on approve, it takes me to their website via a provided redirect_uri which I can not change and has the code in the url. How do I get that code? eg personal.fitnesssync.com?code=asdfghhjkl
Because it is a redirect I can not just grab it as a GET parameter.

Using your code, currently I have to manually copy that code I went to above, enter it into postman authorization with my client_id, client_secret etc and say get new token. I then take that token and manually add it via NullGrantType. That in turn gives me no access to refresh token so I have to do the whole process over again in an hour.

I am very new to this, and your class is the closest I have come to it working (for an hour at least until the token expires :0

Thanks

Token deleted before the new one is confirmed

$this->tokenPersistence->deleteToken();

Hi there!

Checking the order things happen, I noticed that the persistent token is deleted before the new one is confirmed.
By doing it, if something goes wrong when the new token is requested (like a connection timeout or something), you won't be able to try to get the new token anymore, unless you authenticate in the browser again.

Imagine this scenario:

  1. Token is expired
  2. You delete the current token (line 210)
  3. You call $this->requestNewAccessToken(); (line 213)
  4. Something goes wrong in step 3
  5. You don't have the persistent token anymore, so you don't have the refresh token and you are stuck

I'm not sure if deleting the old token is really necessary for other types of token persistence, but talking about FileTokenPersistence, I don't think it is, since the new token will override the old one.

Comprehensive example of the Authorization Code Grant Type?

First off, thanks so much for this library; it's tremendous. We're currently evaluating it as the basis of OAuth2 support in our nascent concrete5 PHP REST API client (which is based off Guzzle Web services.)

I've been around the block with OAuth2 enough that I understand the difference between the flows and grant types, but I'm having real trouble using your Guzzle OAuth2 Subscriber library with the Authorization Code grant type. It looks like all the pieces are there, but I can't fit them together.

You have the token persistence layer and the middleware layers, but I can't seem to understand how to bring them all together on a failure condition. I'd like my API Client library to be able to connect to the remote API, set all the parameters, and have everything magically fall into place (I know, sounds wonderful, right?). But On the first run of my API Client Factory, there obviously won't be any access token. How do I handle this? Do I just check the persistence layer manually on first run?

I can certainly do this, but I where I'm getting further hung up is on this idea of retrying for a new access code. There's a lot of code in the library to do this, but I can't see how this is going to work with the Authorization Code grant type. Can it? For example, if the access token is bad and needs to be retrieved from the server, the middleware layer has to redirect the user to the authorization endpoint. I don't see anywhere in the middleware where this happens, or where this option could be? Am I missing something obvious (very possible.) Also possible that I just am not quite thinking of the flow quite right.

Finally, here is some code that I am currently using. It works, which is great, so I can certainly refactor it and keep moving (it's not pretty, yet.) but I thought I'd show you what we're working with:

Code to Authorization the Client

The user has to manually authorize the client before they can make requests, by clicking a link that takes them into this controller which runs this code. This is using the League OAuth2 Client library.

    if (!$this->request->query->has('code')) {
        $authorizationUrl = $provider->getAuthorizationUrl(); // Note: this MUST come before the session set below
        $oauth2state = $provider->getState();
        $this->app->make('session')->set('oauth2state', $oauth2state);
        return Redirect::to($authorizationUrl);
    } else if (!$this->request->query->has('state') ||
        $this->request->query->get('state') != $this->app->make('session')->get('oauth2state')) {

        $this->app->make('session')->remove('oauth2state');
        $this->error->add(t('Invalid OAuth2 State.'));
    } else {
        $token = $provider->getAccessToken('authorization_code', ['code' => $this->request->query->get('code')]);
        $cache = \Core::make('cache/expensive');
        $item = $cache->getItem('oauth2-access-token');
        $cache->save($item->set($token));

        // Unimportant stuff snipped...
    }

Then, they can make authenticated API calls. This is the PHP Client library that allows them to do that. Any time someone wants to use the api they create a client using the ClientFactory, which runs this code:

    $oauthProvider = $provider->createOauthProvider();

    $cache = \Core::make('cache/expensive');
    $item = $cache->getItem('oauth2-access-token');
    $accessToken = $item->get();
    if ($item->isMiss()) {
        // uh oh what do I do here?!
    }

    if ($accessToken->hasExpired()) {
        $accessToken = $provider->getAccessToken('refresh_token', [
            'refresh_token' => $accessToken->getRefreshToken()
        ]);
        $item = $cache->getItem('oauth2-access-token');
        $cache->save($item->set($token));
    }

    $oauth = new OAuth2Middleware(new NullGrantType());
    $oauth->setAccessToken($accessToken->getToken());
    $stack = HandlerStack::create();
    $stack->push($oauth);
    $httpClient = new \GuzzleHttp\Client([
        'base_uri' => $provider->getBaseUrl(),
        'auth' => 'oauth',
        'handler' => $stack,
    ]);

    $client = new Client($httpClient);

In this example Client is our own lightweight client library, it's not applicable to this discussion. Also the $oauthProvider here is the League Oauth2 client. This setup works – but it could be so much more elegant using your various grant types. Help!

it is not compatible with guzzle >=6

Hi,

I'm using guzzle 6.4 on my project but after using your package I face with

PHP Fatal error: Interface 'GuzzleHttp\Event\SubscriberInterface' not found in /srv/www/application/vendor/kamermans/guzzle-oauth2-subscriber/src/OAuth2Subscriber.php on line 15

after some investigation on the guzzle package github, I noticed this interface has been renamed.

Guzzle 8: The getConfig Method from ClientInterface will be removed

Hi!
Do you already have some migrations plans for the getConfig method which will be removed in Guzzle 8?
It's already marked as deprecated and as far as I have followed the messages from the guzzle team there won't be an alternative to access the "real" config of the client..
Thanks &
Regards Alex

What is the best way to handle temporary failure on refreshing token?

I am working with an OAuth server that is not super reliable and only has a 1 hour token life. This means that, usually within 24-36 hours, I will receive a failure response from the refresh call.

The current OAuth2Handler implementation will ask the TokenPersistenceInterface to delete a token that is expired. That token has the only copy of the refresh token. It looks like it gets exactly one shot to have that refresh call it work. If it works, great! It gives me a new token. If it does NOT work, I've now lost access to my refresh token.

Short of tweaking my TokenPersistenceInterface implementation to not actually delete a token when delete is called on it, are there any other options I'm missing?

Please update v1.0.2 zip files to include latest change.

It seems that v1.0.2 zip files are the very same as for v1.0.1 release. At least "Fix RefreshToken" update is missing from zip files.

Now with composer update project version is updated to v1.0.2 in the lock file, but zip file downloaded doesn't include changes introduced in v1.0.2

Error: Call to undefined method kamermans\OAuth2\GrantType\RefreshToken::getTokenData() in kamermans\OAuth2\OAuth2Handler->requestNewAccessToken() (line 222 of xxx/kamermans/guzzle-oauth2-subscriber/src/OAuth2Handler.php)

Adding some content in the request body on access token retrieval

I need to authenticate on a service which supports standard OAuth2 access token retrieval with basic authentication, but also requires to send some JSON during the same POST request (a second set of login and password, please don't ask why). Can I extend this library in some way in order to implement this (apparently) non-standard requirement?

[Request] Documentation for Additional Request Signers

I just spent close to an hour trying to get OAuth2 working, and it turned out the API I was working with will only accept the Client Credentials (Client ID + Client Secret) as Post Form Data (or Query Strings).

After disabling the exceptions I found this confusing error coming back from the third-party API:

No AuthenticationProvider found for org.springframework.security.authentica (truncated...)

(It was working in Postman, but not through this package)

It looks like out of the box this package uses Basic Auth for Client Credentials, which the third-party API I'm working with does not support. Here's the line in question:

// OAuth2Handler.php
        if ($this->clientCredentialsSigner === null) {
            $this->clientCredentialsSigner = new Signer\ClientCredentials\BasicAuth();
        }

Adding this one line to my code fixed everything for me:

$oauth->setClientCredentialsSigner(new PostFormData());

My full code is below:

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

// Authorization client - this is used to request OAuth access tokens
$reauth_client = new Client([
    // URL for access_token request
    'base_uri' => 'https://third-party-system.fake/api/oauth/token',
]);

$reauth_config = [
    "client_id" => "ABC123",
    "client_secret" => "SUPER-SECRET-CLIENT-SECRET",
];

$grant_type = new ClientCredentials($reauth_client, $reauth_config);

$oauth = new OAuth2Middleware($grant_type);
$oauth->setClientCredentialsSigner(new PostFormData());

$client = new Client([
    'auth' => 'oauth',
]);
$client->getConfig('handler')->push($oauth);

$response = $client->get("https://third-party-system.fake/api/records/list");
$listing = $response->getBody();

I can see this package has BasicAuth, PostFormData, and QueryString signers included. A brief explanation of these in the documentation would be extremely helpful to others who might have faced the same issues I did.

Also not sure if there's anything which can be done to help make OAuth2 debugging easier, the "Unable to request a new access token" exception didn't provide any useful information for troubleshooting. The only thing which worked was spending an hour reverse-engineering the Composer package to find out exactly what was failing and if there was a way to fix it.
Maybe that's more of a Guzzle issue than this specific package? Not sure.

Anyway, it's working flawlessly now, thanks for releasing this! (1 lost hour is still better than implementing OAuth2 the old fashioned way 😄 )

PHP 8.1 show user deprecated warnings on return types

Could these be fixed in a new release:

2023-08-10T16:30:21+02:00 [info] User Deprecated: Method "IteratorAggregate::getIterator()" might add "\Traversable" as a native return type declaration in the future. Do the same in implementation "kamermans\OAuth2\Utils\Collection" now to avoid errors or add an explicit @return annotation to suppress this message.
2023-08-10T16:30:21+02:00 [info] User Deprecated: Method "ArrayAccess::offsetGet()" might add "mixed" as a native return type declaration in the future. Do the same in implementation "kamermans\OAuth2\Utils\Collection" now to avoid errors or add an explicit @return annotation to suppress this message.
2023-08-10T16:30:21+02:00 [info] User Deprecated: Method "ArrayAccess::offsetSet()" might add "void" as a native return type declaration in the future. Do the same in implementation "kamermans\OAuth2\Utils\Collection" now to avoid errors or add an explicit @return annotation to suppress this message.
2023-08-10T16:30:21+02:00 [info] User Deprecated: Method "ArrayAccess::offsetExists()" might add "bool" as a native return type declaration in the future. Do the same in implementation "kamermans\OAuth2\Utils\Collection" now to avoid errors or add an explicit @return annotation to suppress this message.
2023-08-10T16:30:21+02:00 [info] User Deprecated: Method "ArrayAccess::offsetUnset()" might add "void" as a native return type declaration in the future. Do the same in implementation "kamermans\OAuth2\Utils\Collection" now to avoid errors or add an explicit @return annotation to suppress this message.
2023-08-10T16:30:21+02:00 [info] User Deprecated: Method "Countable::count()" might add "int" as a native return type declaration in the future. Do the same in implementation "kamermans\OAuth2\Utils\Collection" now to avoid errors or add an explicit @return annotation to suppress this message.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.