vienthuong / shopware-php-sdk

A PHP SDK for Shopware 6 Admin API
MIT License
112 stars 44 forks source link

The resource owner or authorization server denied the request. Access token could not be verified. #76

Closed gaxweb closed 8 months ago

gaxweb commented 1 year ago

Error code 9, Status 401.

Does the client not make sure that the access token stays valid? It seems like I'm getting a timeout right in the middle of a long running process (after about 623s).

If not, what would be the best practice to keep the token valid and hence the connection alive? Keeping track of the time that has passed seems silly to me.

I'm using the PasswordGrantType.

gaxweb commented 1 year ago

Ok, it seems the reason is that, if you don't explicitly configure the AdminAuthenticator options to retry at least once, it won't do it at all and the 10 minute token will simply expire. That should be made more obvious IMHO.

Should have looked into the logs first. Error still occurring.

gaxweb commented 1 year ago

Can I please get an answer on this:

Does the client not make sure that the access token stays valid?

Do I have to refresh it myself? I can't find an error with the code. It works fine for about 10 Minutes (the default token time limit) and then it throws the error in the title.

SpiGAndromeda commented 1 year ago

The repository methods to fetch data require a Context object. This object requires an AccessToken object and this is generated by using the AdminAuthenticator. That has to be triggered manually.

There is no automatic refresh. Shopware also doesn't provide a refresh token for every grant type. A client credentials authentication token e.g. doesn't contain one.

The AccescToken class has a isExpired method which can be used to check if a new token is required. If the token is expired an there is a refresh token, a RefreshTokenGrantType object can be created to reauthenticate. But this has to be done manually, too.

If the token used in the context object for the request is expired, Shopware will simply return 401 and the SDK will throw that as an exception.

vienthuong commented 1 year ago

@gaxweb

Do I have to refresh it myself? I can't find an error with the code. It works fine for about 10 Minutes (the default token time limit) and then it throws the error in the title.

With PasswordTypeGrantType, its by default alive for 10 minutes. You need to refresh the token afterward using RefreshTokenGrantType as hint from @SpiGAndromeda If you want a longer duration access token, you can use ClientCredentialsGrantType instead

gaxweb commented 8 months ago

This is the code I use to refresh a previously retrieved PasswordGrant token:

protected function refresh(int $time){
    static $seconds = 0;
    if ($seconds === 0 or ($time - $seconds >= 300)) {
        $accessToken = $this->adminClient->fetchAccessToken();

        $grantType = new RefreshTokenGrantType($accessToken->refreshToken);
        $this->adminClient = new AdminAuthenticator($grantType, $this->api_uri, $this->config);

        $this->context = new Context($this->api_uri, $accessToken);
        $this->syncService = new SyncService($this->context);

        $seconds = $time;
    }
}

I'm running this once right at the start to retrieve the access token the 1st time, then in various long running methods again.

After a bit more than 10min, I still get 401 errors like "Access token could not be verified". Do you see a problem with the way I'm doing it? I'm starting to lose my mind. Is it a problem that I request a new one before the 10 min have passed?

Shopware version 6.5.7.3, SDK version 2.0.0

gaxweb commented 8 months ago

After looking at a recent pull request, I've reduced it to this:

public function connect(string $api_uri, string $username, string $password): void {
    $grantType = new PasswordGrantType($username, $password);
    $adminClient = new AdminAuthenticator($grantType, $api_uri, $this->config);
    $this->context = new Context($api_uri, $adminClient->fetchAccessToken());
    $this->syncService = new SyncService($this->context);
}

protected function refresh(int $time){
    if ($this->context->accessToken->isExpired()) {
        $grantType = new RefreshTokenGrantType($this->context->accessToken->refreshToken);
        $adminClient = new AdminAuthenticator($grantType, $this->context->apiEndpoint, $this->config);

        $this->context = new Context($this->context->apiEndpoint, $adminClient->fetchAccessToken());
        $this->syncService = new SyncService($this->context);
    }
}

Still the same error.

gaxweb commented 8 months ago

Oh boy… I was using a MediaService which was still trying to use the old Context object after the refresh…