chillerlan / php-oauth

A fully transparent, framework agnostic PSR-18 OAuth client.
MIT License
32 stars 0 forks source link

[Provider] Garmin: oauth_verifier in params in getRequestAuthorization #4

Closed matamalabs closed 1 month ago

matamalabs commented 4 months ago

To get the User Access Token from Garmin Connect Api, I've used the public method getAccessToken in OAuth1Provider.php. In the public method getRequestAuthorization I've added oauth_verifier in the params:

public function getRequestAuthorization(RequestInterface $request, AccessToken|null $token = null):RequestInterface{
        $token ??= $this->storage->getAccessToken($this->name);

        if($token->isExpired()){
            throw new InvalidAccessTokenException;
        }

        $params = [
            'oauth_consumer_key'     => $this->options->key,
            'oauth_nonce'            => $this->nonce(),
            'oauth_signature_method' => 'HMAC-SHA1',
            'oauth_timestamp'        => time(),
            'oauth_token'            => $token->accessToken,
            'oauth_version'          => '1.0',
                        'oauth_verifier'          => $verifier,
        ];

        $params['oauth_signature'] = $this->getSignature(
            $request->getUri(),
            $params,
            $request->getMethod(),
            $token->accessTokenSecret,
        );

        return $request->withHeader('Authorization', sprintf('OAuth %s', QueryUtil::build($params, null, ', ', '"')));
}

Because without oauth_verifier I got an error in the response from Guzzle.

codemasher commented 4 months ago

Hi, this is not a bug as this library does not offer a Garmin provider class (yet). I'm going to re-label this issue in order to add support for it. Is there a public available API documentation and/or can you provide me with details?

codemasher commented 4 months ago

@matamalabs btw where does the method in your code snippet get the $verifierfrom? It seems that this parameter is undefined there, which will cause the underlying methods to remove it from the request.

matamalabs commented 4 months ago

Hi, this is not a bug as this library does not offer a Garmin provider class (yet). I'm going to re-label this issue in order to add support for it. Is there a public available API documentation and/or can you provide me with details?

The Garmin Connect API is private so I'm not available to provide you. You can register on https://developer.garmin.com/gc-developer-program/health-api/

matamalabs commented 4 months ago

@matamalabs btw where does the method in your code snippet get the $verifierfrom? It seems that this parameter is undefined there, which will cause the underlying methods to remove it from the request.

Actually, I do:

public function getRequestAuthorization(RequestInterface $request, AccessToken|null $token = null):RequestInterface{
        $token ??= $this->storage->getAccessToken($this->name);

        if($token->isExpired()){
            throw new InvalidAccessTokenException;
        }

        $query = $request->getUri()->getQuery();
        $queryList = explode("=", $query);

        $params = [
            'oauth_consumer_key'     => $this->options->key,
            'oauth_nonce'            => $this->nonce(),
            'oauth_signature_method' => 'HMAC-SHA1',
            'oauth_timestamp'        => time(),
            'oauth_token'            => $token->accessToken,
            'oauth_version'          => '1.0',
            'oauth_verifier'         => $queryList[1],
        ];

        $params['oauth_signature'] = $this->getSignature(
            $request->getUri(),
            $params,
            $request->getMethod(),
            $token->accessTokenSecret,
        );

        $header_auth = sprintf('OAuth %s', QueryUtil::build($params, null, ', ', '"'));

        return $request->withHeader('Authorization', $header_auth);
}

From $request param I can get the oauth_verifier, which is added to uri in sendAccessTokenRequest method.

matamalabs commented 4 months ago

$verifier could be an optional param in getRequestAuthorization method to make the $params with oauth_verifier?

codemasher commented 4 months ago

Adding a non-interface parameter to this method is not advised as it is used in several places. This is a provider specific quirk that deviates from the RFC as the oauth_verifier parameter is usually only used during token requests - the getRequestAuthorization() method on the other hand is used for API requests with an existing token.

You can simplify fetching the parameter from the URI btw with the following:

$queryList = QueryUtil::parse($request->getUri()->getQuery());
codemasher commented 4 months ago

Wait, I need to correct myself: the getRequestAuthorization() method is also used in the token request method, where the verifier is added from the incoming callback:

https://github.com/chillerlan/php-oauth/blob/94aa904ccb702baede428d192c50e2408cee3e33/src/Core/OAuth1Provider.php#L203-L219

matamalabs commented 4 months ago

Wait, I need to correct myself: the getRequestAuthorization() method is also used in the token request method, where the verifier is added from the incoming callback:

https://github.com/chillerlan/php-oauth/blob/94aa904ccb702baede428d192c50e2408cee3e33/src/Core/OAuth1Provider.php#L203-L219

In that getRequestAuthorization method from sendAccessTokenRequest method is where I'm needing the oauth_verifier to build the $params array.

In fact, getRequestAuthorization method is only used in sendAccessTokenRequest method.

codemasher commented 4 months ago

In that getRequestAuthorization method from sendAccessTokenRequest method is where I'm needing the oauth_verifier to build the $params array

That is the $_GET parameter from the incoming callback then, which you need to add to the getAccessToken() method as shown in the example:

https://github.com/chillerlan/php-oauth/blob/94aa904ccb702baede428d192c50e2408cee3e33/examples/example-oauth1.php#L57

matamalabs commented 4 months ago

In that getRequestAuthorization method from sendAccessTokenRequest method is where I'm needing the oauth_verifier to build the $params array

That is the $_GET parameter from the incoming request then, which you need to add to the getAccessToken() method as shown in the example:

https://github.com/chillerlan/php-oauth/blob/94aa904ccb702baede428d192c50e2408cee3e33/examples/example-oauth1.php#L57

I do that but I need the oauth_verifier in getRequestAuthorization method to build the $params aray such as:

public function getRequestAuthorization(RequestInterface $request, AccessToken|null $token = null):RequestInterface{
        $token ??= $this->storage->getAccessToken($this->name);

        if($token->isExpired()){
            throw new InvalidAccessTokenException;
        }

        $query = $request->getUri()->getQuery();
        $queryList = explode("=", $query);

        $params = [
            'oauth_consumer_key'     => $this->options->key,
            'oauth_nonce'            => $this->nonce(),
            'oauth_signature_method' => 'HMAC-SHA1',
            'oauth_timestamp'        => time(),
            'oauth_token'            => $token->accessToken,
            'oauth_version'          => '1.0',
            'oauth_verifier'         => $queryList[1],
        ];

        $params['oauth_signature'] = $this->getSignature(
            $request->getUri(),
            $params,
            $request->getMethod(),
            $token->accessTokenSecret,
        );

        $header_auth = sprintf('OAuth %s', QueryUtil::build($params, null, ', ', '"'));

        return $request->withHeader('Authorization', $header_auth);
}

To get the User Access Token for Garmin Connect Api the header "Authorization OAuth..." needs the oauth_verifier to work fine.

codemasher commented 4 months ago

Hmm, let me check what the other OAuth1 providers do with this parameter in the signature array, to see if this could be added in general (this might take me a moment).

matamalabs commented 4 months ago

Hmm, let me check what the other OAuth1 providers do with this parameter in the signature array, to see if this could be added in general (this might take me a moment).

Sure. Thanks.

codemasher commented 4 months ago

Ok that was a larger rework - I've added a separate method for the request headers, as the oauth_verifier is, in fact, part of the signature as per RFC. I can't recall why I still had it in the request URL instead - I think that's a remnant from earlier twitter implementations or something. Funny enough that all of the built-in providers seem to support both (or don't even check for the verifier... who knows).

Can you please check out from dev-main#06f74facb4bbc7167a71a7533bda46832dd660fd and let me know if the update works for you?

matamalabs commented 4 months ago

Ok that was a larger rework - I've added a separate method for the request headers, as the oauth_verifier is, in fact, part of the signature as per RFC. I can't recall why I still had it in the request URL instead - I think that's a remnant from earlier twitter implementations or something. Funny enough that all of the built-in providers seem to support both (or don't even check for the verifier... who knows).

Can you please check out from dev-main#06f74facb4bbc7167a71a7533bda46832dd660fd and let me know if the update works for you?

Hi. Sorry for letting you know so late. The update is working for me.

Thank you so much and great job!

codemasher commented 4 months ago

That's great news! I'm going to tag it as version 1.0.1 then, which you can use in your composer.json. Would you mind sharing your Garmin provider class so that I can add it to the library? Unfortunately applying for API access there has several requirements I cannot fulfil.