Following the Amazon Selling Partner API Doc, I was able to get the LWA access token.
However, I'm getting blocked in making request to the REST API.
https://github.com/amzn/selling-partner-api-docs/blob/main/guides/developer-guide/SellingPartnerApiDeveloperGuide.md#connecting-to-the-selling-partner-api
I tried to use aws-sdk-signer to create a signed request
access_token = 'LWA access token'
signer = Aws::Sigv4::Signer.new(
access_key_id: 'my access id',
region: 'us-east-1',
secret_access_key: 'my access key,
service: 'execute-api',
)
signature = signer.sign_request(
http_method: 'GET',
url: 'https://sellingpartnerapi-na.amazon.com/orders/v0/orders',
headers: {
'host' => 'sellingpartnerapi-na.amazon.com',
'user_agent' => 'test (Language=Ruby)',
'x-amz-access-token' => access_token
})
response = HTTParty.send(:get, 'https://sellingpartnerapi-na.amazon.com/orders/v0/orders', headers: {
'host' => signature.headers['host'],
'user_agent' => 'test (Language=Ruby)',
'x-amz-access-token' => access_token,
'x-amz-content-sha256' => signature.headers['x-amz-content-sha256'],
'x-amz-date' => signature.headers['x-amz-date'],
'Authorization' => signature.headers['authorization'],
})
resposne
{"errors"=>[{"message"=>"Access to requested resource is denied.", "code"=>"Unauthorized", "details"=>"Access token is missing in the request header."}]}
It looks like I'm not signing the LWA access token correctly, but I have no idea what's going on since this is a new API and there's not much implementation especially in ruby.
Would anyone give some directions?
Update: I followed the Singer document
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Sigv4/Signer.html
Aws::Sigv4::Signer
For anyone stumbling across this:
Your problem likely stems from HTTParty (or other HTTP client gems) using Ruby's Net::HTTPHeader behind the scenes.
Net::HTTPHeader capitalizes all request headers before the request is sent and the x-amz-access-token header is case-sensitive.
If you're populating x-amz-access-token with a valid value and still receiving the following error:
{
"message": "Access to requested resource is denied.",
"code": "Unauthorized",
"details": "Access token is missing in the request header."
}
...then you're likely running into this issue.
You can bypass this by overloading Net:HTTPHeader.capitalize like so:
module Net::HTTPHeader
def capitalize(name)
name
end
private :capitalize
end
see also: https://github.com/amzn/selling-partner-api-docs/issues/292#issuecomment-759904882
"Access token is missing in the request header" sounds like something is wrong with your x-amz-access-token. Are you retrieving it like this? https://github.com/ericcj/amz_sp_api/blob/main/lib/sp_api_client.rb#L40
Related
Now I took a sample code of Twitter v2 API from this link. This sample code shows how OAuth and twitter v2 API work for positng a tweet. It works fine with my consumer key and consumer secret.
And I want to simplify the code like below. It assumes that the access token and access token secret are already known and it skips the process of user's approval, like providing the URL that provides PIN.
require 'typhoeus'
require 'json'
consumer_key = CONSUMER_KEY
consumer_secret = CONSUMER_SECRET
token = ACCESS_TOKEN
token_secret = ACCESS_TOKEN_SECRET
consumer = OAuth::Consumer.new(consumer_key, consumer_secret, :site => 'https://api.twitter.com')
options = {
:method => :post,
headers: {
"User-Agent": "v2CreateTweetRuby",
"content-type": "application/json"
},
body: JSON.dump("Hello, world!")
}
create_tweet_url = "https://api.twitter.com/2/tweets"
request = Typhoeus::Request.new(create_tweet_url, options)
access_token = OAuth::Token.new(token, token_secret)
oauth_params = {:consumer => consumer, :token => access_token}
oauth_helper = OAuth::Client::Helper.new(request, oauth_params.merge(:request_uri => create_tweet_url))
request.options[:headers].merge!({"Authorization" => oauth_helper.header}) # Signs the request
response = request.run
puts response
Then, I see the below error message.
ruby test_tweet.rb
/usr/local/lib/ruby/gems/3.1.0/gems/oauth-0.5.10/lib/oauth/request_proxy.rb:18:in `proxy': Typhoeus::Request (OAuth::RequestProxy::UnknownRequestType)
from /usr/local/lib/ruby/gems/3.1.0/gems/oauth-0.5.10/lib/oauth/signature.rb:12:in `build'
from /usr/local/lib/ruby/gems/3.1.0/gems/oauth-0.5.10/lib/oauth/signature.rb:23:in `sign'
from /usr/local/lib/ruby/gems/3.1.0/gems/oauth-0.5.10/lib/oauth/client/helper.rb:49:in `signature'
from /usr/local/lib/ruby/gems/3.1.0/gems/oauth-0.5.10/lib/oauth/client/helper.rb:82:in `header'
from test_tweet.rb:28:in `<main>'
When I used irb and tried step by step, this error happens at oauth_helper.header. As this is the first time to use OAuth API, I may be making some easy mistakes. Does anybody find anything wrong in my code?
I confirmed that my access token and access token secret work at https://web.postman.co/.
Thanks.
You need to insert
require 'oauth/request_proxy/typhoeus_request'
and your code may complete task you desire.
Other lines looks good to me!
In oauth/request_proxy.rb, oauth library check class of request object.
https://github.com/oauth-xx/oauth-ruby/blob/master/lib/oauth/request_proxy.rb
return request if request.is_a?(OAuth::RequestProxy::Base)
klass = available_proxies[request.class]
# Search for possible superclass matches.
if klass.nil?
request_parent = available_proxies.keys.find { |rc| request.is_a?(rc) }
klass = available_proxies[request_parent]
end
raise UnknownRequestType, request.class.to_s unless klass
By requiring 'oauth/request_proxy/typhoeus_request', Typhoeus::Request inherits OAuth::RequestProxy::Base and raising UnknownRequestType error can be avoided.
https://github.com/oauth-xx/oauth-ruby/blob/master/lib/oauth/request_proxy/typhoeus_request.rb
I'm currently trying to create a document and upload it to the SP-API sandbox environment using ruby and HTTP.rb gem. My steps are:
Request the LWA access token by a refresh token
Assume the role and request the STS token
Sign the request header using AWS::SignV4 SDK
Send the POST request to the endpoint /feeds/2020-09-04/documents with body json: { 'contentType' => 'text/tab-separated-values; charset=UTF-8' }
However, SP-API keeps returning "code": "InvalidSignature" to me. But all my other 'GET' requests like get_orders, get_order_items are working correctly.
Here is how I send my request:
#url = '/feeds/2020-09-04/documents'
#body = if sandbox
{ 'contentType' => 'text/tab-separated-values; charset=UTF-8' }
else
{ 'contentType' => 'text/xml; charset=UTF-8' }
end
#request_type = 'POST'
response = http.headers(headers).send(#request_type.downcase.to_sym, request_url, json: #body)
I checked the AWS::Signer::V4 document, turns out I should pass the body into the signer as well.
signer.sign_request(http_method: #request_type, url: request_url, body: #body)
I published the amz_sp_api rubygem that does this, but I would welcome a contribution of the ruby code for encrypting the feed submissions as required by SP-API: https://github.com/ericcj/amz_sp_api/issues/1
I have a laravel application setup and I'm configuring a Discord auth system.
I have sent & requested authorization & recieved a confirmation code back.
When I try to exchange the code for an access token I'm receiving a 400 bad request and I'm not sure why.
I'm new to laravel and can't seem to find any sort of error that may help to pin point the problem.
Controller function that discord redirects after authorization
public function exchange(Request $request) {
//exchange discord code for access_token
$code = Request::get('code',false);
$params = array(
'grant_type' => 'authorization_code',
'client_id' => env('CLIENT_ID'),
'client_secret' => env('OAUTH2_CLIENT_SECRET'),
'redirect_uri' => Request::root().'/discord/return',
'scope' => 'identify guilds guilds.join',
'code' => $code
);
$access_token = Http::withOptions([
'debug' => true,
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/x-www-form-urlencoded'
],
'json' => $params
])->post('https://discordapp.com/api/oauth2/token');
dd($access_token);
return redirect()->route('return.discord',['token' => $access_token->access_token]);
}
I've dumped out $params and all the data is pulling in as it should.
Guzzle dumps this:
[CONNECT] [FAILURE] severity: "2" message: "HTTP/1.1 400 Bad Request " message_code: "400" [MIME_TYPE_IS] message: "application/json" [FILE_SIZE_IS] message: "Content-Length: 26" bytes_max: "26" [PROGRESS] bytes_max: "26"
I know that 400 bad request means an issue with the data being sent along, but I can't figure out what I'm doing that's not producing the correct result. The documentation states the required content type which I have set, so I'm really scratching my head here.
Any help would be much appreciated.
Here's the Discord API documentation:
https://discord.com/developers/docs/topics/oauth2#authorization-code-grant
To anyone looking for an answer...
The "invalid grant" error is due to an issue with the Discord API and non-standard requests. I could not get the Laravel HTTP class request to work for exchanging the access token. Instead, I used native php cURL within Laravel.
As for the issue with receiving '400 bad request', that was due to using incorrect function withOptions() as oppossed to asJson()->withHeaders(). Thanks #Zachary Craig!
However, I could not use this in the end due to the problem described above.
If any others find a better solution, let me know :)!
For all who have the same problem.
The solution is to use ->asForm() before the post. This changes the handling of the data from json to form_params. This way the Discord API accepts the parameters.
return Http::asForm()->post($this->endpoint.'/oauth2/token', $data);
I have a mobile app that is signin in with google and sending a server auth code to my backend app.
I want to use this code, along with the client secrets from the google developer console, to retrieve a refresh code for retrieving data from google drive when the user is offline.
Google provides an client for auth calls in ruby, but it seems not to be maintained lately and I could not see a way to do this kind of authorisation in the docs.
In the documentation, I could find an example of how to do this on python:
from oauth2client import client
# Exchange auth code for access token, refresh token, and ID token
credentials = client.credentials_from_clientsecrets_and_code(
CLIENT_SECRET_FILE,
['https://www.googleapis.com/auth/drive.appdata', 'profile', 'email'],
auth_code)
I would like to do this in ruby through a post to their https://www.googleapis.com/oauth2/v4/token endpoint. Here is what I've tried so far:
require 'uri'
require 'net/http'
require 'json'
url = URI("https://www.googleapis.com/oauth2/v4/token")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
headers = {'Content-Type': 'application/json'}
request = Net::HTTP::Post.new(url.request_uri, headers)
request.body = {
code: "#{server_auth_code_sent_to_api}",
client_id: "#{client_id_from_developer_console}",
client_secret: "#{client_secret_from_developer_console}",
grant_type: 'authorization_code',
redirect_url: '',
}.to_json
response = http.request(request)
puts JSON.parse(response.read_body)
But I keep getting the following error:
{
"error": "unsupported_grant_type",
"error_description": "Invalid grant_type: "
}
Does anybody has an idea on what I'm doing wrong, or has a working example on how to do this kind of authorisation?
Thanks in advance.
In case somebody stumbles here with a similar problem, what caused the request to fail was the Content-Type, and not the grant_type parameter.
Digging around in the code for the client library I saw that they use application/x-www-form-urlencoded the endpoint expects a application/x-www-form-urlencoded content type. I adjusted my code accordingly and was able to get a successful response with the valid credentials and token.
Here follows the resulting code:
require 'uri'
require 'net/http'
require 'json'
url = URI("https://www.googleapis.com/oauth2/v4/token")
params = {
"code" => "#{server_auth_code_sent_to_api}",
"client_id" => "#{client_id_from_developer_console}",
"client_secret" => "#{client_secret_from_developer_console}",
"grant_type" => "authorization_code",
"redirect_url" => "#{redirect_url_from_developer_console}",
}
response = Net::HTTP.post_form(url, params)
puts JSON.parse(response.read_body)
I am trying to implement facebook authentication for an app with warden, after the user allows facebook auth and redirects to my app callback with the token I get a 400 while consuming the api. My warden strategy is this:
class Facebook < Warden::Strategies::Base
def client
#client ||= OAuth2::Client.new MyApp::Facebook::AppID, MyApp::Facebook::AppSecret, :site => 'https://graph.facebook.com'
end
def params
#params ||= Rack::Utils.parse_query(request.query_string)
end
def authorize_url
client.web_server.authorize_url :redirect_uri => request.url, :scope => 'email,publish_stream'
end
def authenticate!
throw(:halt, [302, {'Location' => authorize_url}, []]) unless params['code']
facebook = client.web_server.get_access_token params['code'], :redirect_uri => request.url
rescue OAuth2::HTTPError => e
puts e.response.body
end
end
Strategies.add :facebook, Facebook
The result of printing the response body is this:
{"error":{"type":"OAuthException","message":"Error validating client secret."}}
I am pretty shure the app id and app secret are the ones provided by FB.
Thanks.
I've seen that error message many times. Here are the things I would double check:
your domain is the same as what you listed in the facebook callback url
the app id is correct (actually print this out on a page, sometimes y
the app secret is correct
Add redirect_uri while creating the object of facebook that will fix the issue.
Redirect the user to https://www.facebook.com/dialog/oauth?client_id=YOUR_APP_ID&redirect_uri=YOUR_URL
After user click allow, it'll hit our Redirect Uri
At that point we'll get the code and we need to do a server side HTTP Get to the following Url to exchange the code with our oAuth access token:
https://graph.facebook.com/oauth/access_token?
client_id=YOUR_APP_ID&redirect_uri=YOUR_URL&
client_secret=YOUR_APP_SECRET&code=THE_CODE_FROM_ABOVE
Now at step 3, I kept on getting Http 400 response back.
So after some research, I found out that on that redirect_uri that we submitted on step 3 doesn't do anything but validate the request. Thus, the value need to match with step 2.
I also get the same error and i resolved by doing as below:
double check your client_id, client_secret, redirect_uri.
Add Accept: "application/json" header to thye request
fetch(
`https://graph.facebook.com/v15.0/oauth/access_token?client_id=${process.env.FACEBOOK_APP_ID}&redirect_uri=${process.env.FACEBOOK_REDIRECT_URI}&client_secret=${process.env.FACEBOOK_APP_SECRET}&code=${code}`,
{
method: "GET",
headers: {
Accept: "application/json",
},
}
)