oauth2-azure icon indicating copy to clipboard operation
oauth2-azure copied to clipboard

Getting a resource owner fails with refreshed access token (missing data)

Open davidloubere opened this issue 5 years ago • 4 comments

I need to implement access token refreshing in a Symfony project. While trying to retrieve the resource owner in passing the refreshed access token, an exception is raised from the Azure provider:

Argument 1 passed to TheNetworg\OAuth2\Client\Provider\Azure::createResourceOwner() must be of the type array, null given, called in /application/vendor/thenetworg/oauth2-azure/src/Provider/Azure.php on line 65

Here is the implementation done in an event subscriber:

namespace App\Security\Azure;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use TheNetworg\OAuth2\Client\Provider\Azure;

class ResponseSubscriber implements EventSubscriberInterface
{
    /**
     * @var \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface
     */
    private $tokenStorage;

    /**
     * @var \TheNetworg\OAuth2\Client\Provider\Azure
     */
    private $azureProvider;

    public function __construct(TokenStorageInterface $tokenStorage, Azure $azureProvider)
    {
        $this->tokenStorage = $tokenStorage;
        $this->azureProvider = $azureProvider;
    }

    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::RESPONSE => [
                ['refreshOAuthToken'],
            ],
        ];
    }

    public function refreshOAuthToken(FilterResponseEvent $event): void
    {
        // ...

        $user = $token->getUser(); // $token is retrieved from the $tokenStorage property 

        if ($user instanceof User) {
            $currentAccessToken = $user->getAccessToken();

            if (true === $currentAccessToken->hasExpired()) {
                /** @var \TheNetworg\OAuth2\Client\Token\AccessToken $accessToken */
                $accessToken = $this->azureProvider->getAccessToken(
                    'refresh_token', [
                        'refresh_token' => $currentAccessToken->getRefreshToken(),
                    ]
                );

                $resourceOwner = $this->azureProvider->getResourceOwner($accessToken);

                // ...
            }
        }
    }
}

The new access token is not "hydrated" as the previous one. It lacks of the data which are expected in the provider:

public function getResourceOwner(\League\OAuth2\Client\Token\AccessToken $token)
{
    $data = $token->getIdTokenClaims(); // <=== $data is null
    return $this->createResourceOwner($data, $token);
}

Here's a screenshot of the access tokens (second dump shows the refreshed one which lacks of data):

image

It looks like a bug to me. But perhaps something is happening behind the hood I don't get?

davidloubere avatar Mar 21 '19 10:03 davidloubere

Right, so the identity is only being obtained from the id_token, but not from access_token due to the way the AccessToken class is initiated... This is a good catch!

hajekj avatar Mar 21 '19 15:03 hajekj

I think this will need a slight change, in how the id_token is passed into the AccessToken class - so that we can pass it externally in case of refresh...

hajekj avatar Mar 21 '19 15:03 hajekj

Oh I see, thank you. Moving the logic defined in the constructor into a separate class may help. This would enable to pass the options array after class instantiation.

davidloubere avatar Mar 22 '19 08:03 davidloubere

Hi how can this be solved: the issue with the access token being incomplete. i don't seem to get it to work

serge512 avatar Sep 12 '20 21:09 serge512