amzn / selling-partner-api-models

This repository contains OpenAPI models for developers to use when developing software to call Selling Partner APIs.
Apache License 2.0
611 stars 733 forks source link

Error 'invalid_grant' upon seller authorisation (PHP, JavaScript) #807

Closed winterstefan closed 2 years ago

winterstefan commented 3 years ago

Hi there,

I'm trying to adapt the newly published SP-API for my use-case, having a setup with PHP7 (server-side) and JavaScript (client-side). Unfortunately, right upon authorising my application (still in draft state) with a seller's account (or even my own developer account) fails.

tl;dl The HTTP request of trading a short code for a long-living one fails, the error mentions my code is wrong somehow. Am I missing a step?

My use-case

My problem

The request for exchanging the code fails. See developer docs (in step 4, see section To exchange an LWA authorization code for an LWA refresh token.

The response contains the following hint:

{"error_description":"The request has an invalid grant parameter : code","error":"invalid_grant"}

My source code

Part 1 - Javascript

This snippet handles clicking on a button thus opening the Amazon LWA popup where the seller authorises my application.

    const handleClick = () => {
        if (!window.amazon) {
            console.log('window.amazon not found!')
        }

        const options = {
            scope: 'profile',
            response_type: 'code',
        }

        window.amazon.Login.setClientId(
            'amzn1.application-oa2-client.XXXX'
        )
        window.amazon.Login.authorize(options, (response) => {
            if (response.error) {
                console.warn('Authorization failed!', response.error)
                return
            }

            // Output:
            // {status: "complete", code: "AXXXX", scope: "profile", onComplete: ƒ}
            console.log('Authorization succeeded', response)

        })
    }
Part 2 - PHP

The response.code from above is taken and send in the request mentioned above (developer docs, step 4, first request).

For the sake of simplicity, I'm pasting request and response information created with curl. That produces the same error as by using PHP.

CURL command - As I read the documentation, it contains all required elements and config.

curl --verbose --location --request POST 'https://api.amazon.com/auth/o2/token' \
--header 'Content-Type: application/x-www-form-urlencoded;charset=UTF-8' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'code=AXXXX' \
--data-urlencode 'client_id=amzn1.application-oa2-client.XXXX' \
--data-urlencode 'client_secret=0XXXX' \
--data-urlencode 'redirect_uri=https://my.redirect.example.com'

Request information - gained from CURL verbose output

> POST /auth/o2/token HTTP/1.1
> Host: api.amazon.com
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Type: application/x-www-form-urlencoded;charset=UTF-8
> Content-Length: 2XX

Response information - gained from CURL verbose output

< HTTP/1.1 400 Bad Request
< Server: Server
< Date: Wed, 25 Nov 2020 XXXX GMT
< Content-Type: application/json;charset=UTF-8
< Content-Length: 97
< Connection: keep-alive
< x-amz-rid: VXXXX
< x-amzn-RequestId: 0XXXX
< X-Amz-Date: Wed, 25 Nov 2020 XXXX GMT
< x-amzn-ErrorType: OA2InvalidGrantException:http://internal.amazon.com/coral/com.amazon.panda/
< Cache-Control: no-cache, no-store, must-revalidate
< Pragma: no-cache
< Vary: Content-Type,Accept-Encoding,X-Amzn-CDN-Cache,X-Amzn-AX-Treatment,User-Agent

{"error_description":"The request has an invalid grant parameter : code","error":"invalid_grant"}

Summary

I followed the official documentation as accurate as I could. I cannot preclude having made a mistaking in configuring IAM, account, keys and stuff, (to be honest, I hope that this ist just a small config issue)but I double-checked every part of that documentation about it.

So the obvious question: Could anybody shed some light for me what possible issues could lead to this error?

Am I using the wrong origin for the code? Have I forgotten an important step somehow?

rogersv commented 3 years ago

Do you actually get a value in forcode? That is the standard name of the parameter but amazon uses spapi_oauth_code in the callback.

winterstefan commented 3 years ago

@rogersv Thanks for the answer! Indeed, the response object for the first call contains (just) the response.code with an actual token. That was one point that confused me, because the documentation states the occurance of both an MWS and SP token.

Do you have an idea why?

I'm using the script https://assets.loginwithamazon.com/sdk/na/login1.js as stated in the documentation. But the same documentation, just one step forward, also mentions response.code to be passed from the JavaScript snippet to the server-side code (see here).

  <!-- Pass response.code to your server, and use it to request refresh and
  access token. -->
rogersv commented 3 years ago

Sorry, I do not know. I did not used that script. It seems to be correct with code in this case.

gasper-vrhovsek commented 3 years ago

Hi @winterstefan ,

i am also in the process of implementing the sp-api. What i noticed about calling the /apps/authorize/consent?application_id endpoint is the following:

Let me know if you manage to authenticate successfully, as we're also having issues with it.

winterstefan commented 3 years ago

Hi @gasper-vrhovsek, thanks for reaching out!

In short

By making the necessary HTTP calls on my own (PHP or CURL) I'm still unable to successfully accessing the SP-API. When using the JavaScript SDK, I'm getting 'some kind of token' and I'm able to fetch the sellers profile information (e.g. their primary email address). Nevertheless, I'm unable to use this token in the SP-API part. Reason: The request signature of the SP-API request is marked as invalid.

JavaScript part (authentication)

Actually I want to have most of this part on server-side. But for the sake of having at least some kind of working example, I fully used the JavaScript SDK. In short, here you can view the JS code that works for me:

Assumption: I have a valid code for using it when calling a SP-API endpoint.

The SP-API part

Here I'm using a Guzzle client to make my request in PHP. For signing this request, the official AWS PHP SDK is in use (because both support / need the v4 of request signing).

Here too I tried to stick to the documentation as tightly as possible. But I cannot get further than receiving an signature error. Since this is another dead-end for me, I also tried getting help in the Amazon developer forum as well.

Feel free to visit the forum for a detailed description. If you don't have access, here's a short summary.

The request (php)
    $credentials = new Credentials($options['aws_user_key'], $options['aws_user_secret']);
    $signatureV4 = new SignatureV4($options['service'], $options['region']);

    $request = new Request(
        'POST',
        'https://sellingpartnerapi-eu.amazon.com/reports/2020-09-04/reports',
        [
            'x-amz-access-token' => $options['seller_access_token'], // retrieved from JS SDK after seller has approved access to its accoun
            'user-agent' => 'My Tool/0.0.1',
        ],
    );

    $request = $signatureV4->signRequest($request, $credentials);
    $client = new Client();

    $requestOptions = [
        'Content-Type' => 'application/json',
        'Host' => 'https://sellingpartnerapi-eu.amazon.com',
        'json' => [
            'reportType' => 'GET_FLAT_FILE_OPEN_LISTINGS_DATA', // inventory data
            'marketplaceIds' => 'A1PA6795UKMFR9', // german marketplace
        ],
    ];

    try {
        $client->send($request, $requestOptions);
    } catch (ClientException $e) {
        $exceptionRequest = $e->getRequest();
        echo sprintf("\nURI: %s", $exceptionRequest->getUri());
        foreach ($exceptionRequest->getHeaders() as $name => $values) {
            echo sprintf("\n%s: %s", $name, implode(',', $values));
        }
        echo sprintf("\n\nMessage: %s", $e->getMessage());
    } 
The response

I masked credentials. In detail, 'just' the signature itself seems to be wrong. All other params (like described in the guidelines from this very repository look fine to me.

But the great question: What's wrong with this signature? I just use the official AWS SDK for creating the signature, so having a wrong HMAC algorithm should not be the problem. Also, I reached out to Amazon support for actually debugging this very request. They didn't.

URI: https://sellingpartnerapi-eu.amazon.com/reports/2020-09-04/reports
Content-Type: application/json
Host: sellingpartnerapi-eu.amazon.com
x-amz-access-token: Atza|IwEBIAtelq1WRXXXX
user-agent: My Tool/0.0.1
X-Amz-Date: 20201120T142538Z
Authorization: AWS4-HMAC-SHA256 Credential=AKIA3HD5GN6XXXXX/20201120/eu-west-1/execute-api/aws4_request, SignedHeaders=host;x-amz-access-token;x-amz-date, Signature=ca61d60425bc5a6907f2f98f89bb4dXXXX
Message: The request signature we calculated does not match the signature you provided.

Appendix

Just for the sake of completeness: I debugged the hell out of the mentioned SP-APi request. Here are some samples.

I altered most of the params specified by the docs (credential scope and auth header). With that, I tried to double-check if my originally sent information where correct.

Action Response
Using invalid AWS region superman Credential should be scoped to a valid region, not 'superman'.
Using invalid Service superman Credential should be scoped to correct service: 'execute-api'.
Using invalid AWS user key The security token included in the request is invalid.

Nevertheless, altering the HTTP header x-amz-access-token does still deliver the signature error from above. So my conclusion is that this token is not valid. I used just the Atza| style token I received via the JavaScript sdk

winterstefan commented 3 years ago

Ah, almost forgot:

I'm very confused by what the prerequisites have to be for using the production version of the SP-API. Here some thoughts:

@gasper-vrhovsek You mentioned having an application published but oAuth URLs in draft state.

  1. What do you mean by an published application in detail? Is it available in the Amazon App Marketplace? Is there another state of being published?
  2. How can an oAuth URL be just a draft?

For my understanding:

Does anybody have valid information about what steps have to be taken with the application to successfully being able to either use Sandbox or (even better) the Production env?

gasper-vrhovsek commented 3 years ago

Hi @winterstefan

I'll go from top to bottom and try to answer as best i can :)

As @rogersv mentioned earlier, here it's strange to just get code instead of something like spapi_oauth

I would assume they just save the spapi_oauth value in a new code field and return that.

retrieveToken() Returns a very long string always starting with Atza|XXXXXXX Don't know if thats the so called refresh token But going to SellerCentral -> Developer Central -> clicking authorizing for my application, I will get a refresh token (for use with eg the Marketplace API). This refresh token also starts with Atza|XXXXXXX. So I guess mine is a refresh token. ^^

This is where i have problems. Without using the JavaScript lib, my requests to /auth/o2/token get rejected with:

{
  "error_description": "Not authorized for requested operation",
  "error": "unauthorized_client"
}

Seeing you have more success with the javascript lib i'll try that also.

Here too I tried to stick to the documentation as tightly as possible. But I cannot get further than receiving an signature error. Since this is another dead-end for me, I also tried getting help in the Amazon developer forum as well.

We weren't yet trying the PHP SDK, but we did try the Java one. We could successfully self-authorize and use the API, but when trying to authorize as other sellers (which have already approved us via the old MWS authentication flow) using the AuthorizeApi class we are getting such error responses:

{
  "errors": [
    {
      "code": "InvalidInput",
      "message": "Developer ID 35XXXXXXXX is not associated with the application id.",
      "details": ""
    }
  ]
}

Regarding this issue, we got a response from amazon support saying, our app should be fully published in Amazon Appstore Marketplace, as our is in draft, pending approval. But the only changes that are in draft are the Oauth URL fields in the application form. That is actually also the answer to your question about our application state.

@gasper-vrhovsek You mentioned having an application published but oAuth URLs in draft state. What do you mean by an published application in detail? Is it available in the Amazon App Marketplace? Is there another state of being published? How can an oAuth URL be just a draft?

We had the application already published, bit it was a MWS app without the SP-API support. Then we changed the app type to MWS & SP-API (hybrid) and published it. I haven't got more info about the process which was taken as i was not at this company at the time. Well after that, we entered the Oauth url settings into our application and those changes are now in draft pending approval.

I'll be trying the Javascript SDK today, i'll post my findings here.

winterstefan commented 3 years ago

Thanks for the awesome reply @gasper-vrhovsek!

Okay now I understand the 'the oauth url is in draft', ty. Makes sense since your application was published before. My use-case right now is a blank new application (registered as hybrid with MWS and SP-API) where I'm trying to use the SP-API for the very first time. So the entire application is in draft state and hasn't been published in the app store at all.

⚠️ I finally was able to get a valid refresh token by using the HTTP endpoint /auth/o2/token. I'll post a fresh new comment right below for keeping it structured and readable (hopefully^^).

Whilst searching for a possible solution, I stumbled across this posting in the Amazon forum, with that username I guess it's your post? To be honest, it indeed sounds like a configuration issue. But I do not know any location where you would supposed to 'link' the developer ID with the application Id. Did the support help in any way?

In that forum, another kind person replied trying to help: posting in amazon forum. In the comments of the PHP code, he describes where to get the specific data from. Maybe this helps you for double-checking with yours?

Maybe you're sending the wrong client_id / client_secret pair to the oauth endpoint? I'm using the amzn1.application-oa2-client. At least I got really confused about client_id from AWS, from LWA and from my SellerCentral.

winterstefan commented 3 years ago

Another day, another approach (I guess). After lots of debugging, I'm not so sure about the JavaScript SDK anymore. Even I understand the documentation that I can either use the JavaScript SDK or the manually concatenated consent URL, there seem to be differences.

Summary

So now I understand the auth part just a bit more (hence: I got a valid refresh token and was able to receive an auth token right on demand). That gets me back to the other problem with calling an SP-API endpoint (see my other posting in this thread).

I reviewed every step and checked my request data. I'm just every time receiving the error, that my signature isn't correct. I'm quite frustrated 😇 .

@gasper-vrhovsek If I can provide any more information that may help you getting through the auth process, keep me posted!


Approaches

Approach 1️⃣: auth code via JS, refresh token via PHP (❌ not working ❌)

Steps

Result

Conclusion

Approach 2️⃣: auth code via JS, refresh token via JS (❌ not working ❌)

Steps

Result

Conclusion

Approach 3️⃣: Manual concatenated URI, refresh token via PHP (✅ works for me ✅)

Steps

Result

gasper-vrhovsek commented 3 years ago

Hey @winterstefan

To be honest, it indeed sounds like a configuration issue. But I do not know any location where you would supposed to 'link' the developer ID with the application Id. Did the support help in any way?

We got a reply regarding the issue you linked (that was reproduced using the Java SDK and trying to authenticate with some of our seller partner credentials), amazon support said the issue is the draft state of our app. We asked them if they could speed up the approve process. I'll be testing it again once they do.

In that forum, another kind person replied trying to help: posting in amazon forum. In the comments of the PHP code, he describes where to get the specific data from. Maybe this helps you for double-checking with yours?

Thanks for that, i'll be taking a look.

Maybe you're sending the wrong client_id / client_secret pair to the oauth endpoint? I'm using the amzn1.application-oa2-client. At least I got really confused about client_id from AWS, from LWA and from my SellerCentral.

I got my client_id and client_secret from the seller central, under develop apps and by clicking the "LWA credentials" next to the our app. It also starts with amzn1.application-oa2-client

I'm just able to authorize my own developer account (I guess because my app is in DRAFT state). Any other seller account fails with error code M9999 right in the Amazon UI for authorization.

I got those M9999 errors using the generated Java SDK (AuthorizeApi class). I'm still waiting for our app to be published to test that again.

Approach 3️⃣: Manual concatenated URI, refresh token via PHP (✅ works for me ✅)

I can get the spapi_oauth code by calling the /auth/o2/token endpoint, it works with a manually generated URL. I have issues in step 4. where i should exchange that code for the two tokens (gives us the "Not authorized for requested operation" error). I'll be trying the Javascript SDK today. Might be, i'll have to open another ticket regarding the token exchange errors we have...

winterstefan commented 3 years ago

Hello @gasper-vrhovsek

okay, that sounds legit. I hope you find more insights when using the Javascript SDK and getting the application approved. 👍

I got those M9999 errors using the generated Java SDK (AuthorizeApi class). I'm still waiting for our app to be published to test that again.

That indeed IMHO is the error you receive if trying to authorize an 'external seller account' with your application whilst in draft state. I also am just permitted to use my own 'developer seller account' for autorizing the app with. So agreed, getting the app out of draft state should help.

Just one question about the phraseology:

amazon support said the issue is the draft state of our app. We asked them if they could speed up the approve process

I'm still waiting for our app to be published to test that again.

You're speaking of approve and publish. Do you mean the process of making the application generally available in the Amazon AppStore? Or is there any other process of 'getting out of the DRAFT state'?

That's the point where I got really confused in the first place: Our own application is just a prototype right now (because we started implementing the SP-API, we don't have an existing app with MWS integration). So asking Amazon to publish this incomplete application in the App Store would be wrong. But I'd love using the production SP-API with an actual seller of mine as a test. Since that's not possible because of the DRAFT state (at least this is my understanding of this M9999 error), I wonder if there's an intermediate step between 'in draft mode' and 'published in App Store'.

gasper-vrhovsek commented 3 years ago

Hi @winterstefan

Yeah, i'm also generally confused, sorry. I'll just paste a screenshot of the app status in seller central: Screenshot 2020-12-04 at 15 25 48

I should say we're waiting for amazon to publish the changes we have in draft.

github-actions[bot] commented 2 years ago

This is a very old issue that is probably not getting as much attention as it deserves. We encourage you to check if this is still an issue after the latest release and if you find that this is still a problem, please feel free to open a new issue and make a reference to this one.