How do I interpret this header in this curl request? - ruby

I'm looking at a curl request that has the following as its header. What does this mean?
curl -H 'Authorization:token token="[SOME_VALUE]"' 'https://myurl.com'
Furthermore I'm trying to use RestClient to request this URL from ruby. https://github.com/rest-client/rest-client
Normally in headers it's just a key:value, but here this seems different.

It looks like the API you want to use is adopting the HTTP Token authentication RFC.
This document was a draft and it never turned into an official standard, but there are some APIs that are using it.
GET /resource/1 HTTP/1.1
Host: example.com
Authorization: Token token="h480djs93hd8",
coverage="base",
timestamp="137131200",
nonce="dj83hs9s",
auth="djosJKDKJSD8743243/jdk33klY="
You can pass custom headers to RestClient using the header option.
api_token = "xyz"
RestClient.get "http://example.com/resource", { :Authorization => %Q{token token="#{api_token}"} }
I used %Q to allow interpolation. If it's unclear to you, you can also use something like
api_token = "xyz"
RestClient.get "http://example.com/resource", { :Authorization => 'token token="%s"' % api_token }

It will be same as key value pair. Here the key is Authorization and the value is token token="[SOME_VALUE]". That should be something like below as ruby hash copied from here.
{:Authorization => 'token token="[SOME_VALUE]"'}

You can use some additional information in headers, not just key:value. For example:
Accept: text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c
Authorization:token token="[SOME_VALUE]" can be used with API with token based authentication.
Token based authentication is when an API client uses a token identifier to make authenticated HTTP requests.
You can read more about this type of authentication here: http://blog.codeschool.io/2014/02/03/token-based-authentication-rails/

Related

Ruby GET NET HTTP request does not work with AUTHORIZATION and ACCEPT when passed in a header

I've been using the code below to call a third party API . This code works fine (i've changed the url and the credentials but the structure of the code is the same) :
require 'base64'
require 'httparty'
require 'json'
######################################################################
# Get the token first
######################################################################
consumer_key = "my_key"
consumer_secret = "my_secret"
credentials = Base64.encode64("#{consumer_key}:#{consumer_secret}").gsub("\n", '')
url = "https://mysite/token"
body = "grant_type=client_credentials"
headers = {
"Authorization" => "Basic #{credentials}",
"Content-Type" => "application/x-www-form-urlencoded;charset=UTF-8"
}
r = HTTParty.post(url, body: body, headers: headers)
bearer_token = JSON.parse(r.body)['access_token']
######################################################################
# Use the token in a call as authorisation header
######################################################################
api_url = "https://apisite/the_value_i_am_looking_for_in_the_api"
url = URI.parse(api_url)
req = Net::HTTP.new(url.host, url.port)
req.use_ssl = true
# If we are just passing a key that doesn't need to be in the token format
headers = {
'Authorization' => "Bearer #{bearer_token}"
}
# Get the response back (he data is in the response body: resp.body )
resp = req.get(url, headers)
My issue is that the API providers have changed their API so you now need to pass an "accept" into the call via the header. I used POSTMAN to make the call, added the accept to the header and was able to get it working without issue. So far so good.
I then changed my ruby code to extend the headers section to include the accept, using the code below:
headers = {
'Authorization' => "Bearer #{bearer_token}",
'Accept' => 'application/vnd.bluebadge-api.v1+json'
}
I've not added an accept to a header before so I may have gotten the syntax wrong.
However, this returns an unauthorised 401 response code:
#<Net::HTTPUnauthorized 401 Unauthorized readbody=true>
I thought I might have the credentials wrong so remove the accept, try again and this changes to a 406 response code:
#<Net::HTTPNotAcceptable 406 Not Acceptable readbody=true>
If I examine the response I get the message I would expect that the accept header is not the supported version. So I know the credentials are correct (and the fact they match the postman credentials which works):
"{\"apiVersion\": \"1\",\"context\": null,\"id\": null,\"method\": null,\"error\": {\"code\": null,\"message\": null,\"reason\": \"Accept header version is not a currently supported version.\",\"errors\": null}}\n"
So I know all my credentials are correct because I've copied them into the postman request which works with no errors. The value for the accept header is correct because I copied that from a working postman request too.
I am at a loss for why this wouldn't work.
I've looked through the NET HTTP library and cant find anything to help me there. I've seen a couple of posts elsewhere which I've tried and they haven't worked either.
I appreciate any help in trying to solve this.
Found the problem. I was using the credentials from the production environment to get the token then trying to query the test environment API. In my defence they look very similar (only 3 characters different). I think I had a case of the code blindness.
The code I posted does work when I put the correct URL for the environments.
I also found that I could use this:
uri = URI.parse("https://myapi/some_text")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.request_uri)
request["Authorization"] = "Bearer #{bearer_token}"
request["Accept"] = "application/vnd.bluebadge-api.v1+json"
response = http.request(request)
Or using HTTParty like this:
response = HTTParty.get('https://myapi/some_text', {
headers: {"Authorization" => "Bearer #{bearer_token}", "Accept" => "application/vnd.bluebadge-api.v1+json" }
})
I would prefer the format of my orginal code or the HTTparty code because it is easy to see from the code that you're passing headers. Hopefully this will help others to double check their authorization credentials...

opt_fields not working with Asana API but url works in the Asana explorer

I'm using Ruby starting from the "hello world" example. Hello world works find. I'm trying to get this GET to work:
GET /projects/192372431230306/tasks?opt_fields=id,assignee,due_on,name,notes&limit=10&completed_since=now
It works exactly as expected in the Asana API explorer.
I'm using the same URI in my code:
uri = URI.parse("https://app.asana.com/api/1.0/projects/192372431230306/tasks?opt_fields=id,assignee,due_on,name,notes&limit=10&completed_since=now")
It still returns ID and Name correctly, but it's not what I want.
I can't see why it works in the explorer but not in the GET request. I am using the personal token and the explorer uses OAuth.
My personal token is set correctly before this code. I can create tasks, get projects, get tasks. I just can't more fields in this query like the API explorer.
Added Code:
uri = URI.parse("https://app.asana.com/api/1.0/projects/192372431230306/tasks?opt_fields=id,assignee,due_on,name,notes&limit=10&completed_since=now")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
# set up the request
header = {
"Content-Type" => "application/json"
}
req = Net::HTTP::Get.new(uri.path, header)
req.basic_auth(personal_access_token, '')
res = http.start { |http| http.request(req) }
body = JSON.parse(res.body)
puts "projects: #{body['data']}"
There isn't quite enough code here to know what's wrong. It sounds like your code is working to some degree, but maybe there's something different between using a personal token as opposed to OAuth. According to the Asana API documentation:
Personal Access Tokens should be used similarly to OAuth access tokens when accessing the API, passing them in the Authorization header:
curl -H "Authorization: Bearer ACCESS_TOKEN" https://app.asana.com/api/1.0/users/me
So in addition to the URL, you need to use the correct headers. I'm not sure what HTTP library you use, but here's how I would do it with HTTParty:
require 'uri'
require 'httparty'
uri = URI.parse("https://app.asana.com/api/1.0/projects/192372431230306/tasks?opt_fields=id,assignee,due_on,name,notes&limit=10&completed_since=now")
task = HTTParty.get(uri,
:headers =>
{'Authorization' => "Bearer ${ENV[ACCESS_TOKEN]}"}
).parsed_response
If you put your personal access token in the ACCESS_TOKEN environment variable, that should correctly authenticate you for the request.
Still, it seems like there's something else missing if you can get all the fields. Maybe your personal access token is associated with a different account than your OAuth access token? It might help to check the results of /users/me/ route?

How to set the body of a POST request using Ruby Mechanize?

How can you set the body of a POST request using the Ruby Mechanize gem. I know you can do
mechanize.post(url, query, headers)
but I want to set the body of the POST request with a JSON string. Is that possible? So, similar to something like this with jQuery:
$.ajax({
type: 'POST',
url: 'myurl',
data: "{'key1':'value1','key2':'value2'}",
...
});
I don't really like the answer you linked to in your comment because it employs to_json() which is a rails method, and the tags for your question do not indicate that your question pertains to rails. In any case, I think the answer needs some discussion.
Here is the mechanize method:
Mechanize#post(url, query, headers)
...and your stated goal is:
I want to set the body of the POST request
Mechanize#post() allows you to set the body of the request to anything you want, but you also have to consider the question:
What is the server side expecting?
You gave an example of a jquery ajax() request for what you want to do. jquery uses the following default Content-Type header when sending an ajax() request:
application/x-www-form-urlencoded; charset=UTF-8
That tells the server that the body of the post request is going to be written in a specific secret code. Well, it's not much of a secret; it looks like this:
name1=val1&name2=val2
That secret code's name is x-www-form-urlencoded. Because the server is given the name of the secret code in the Content-Type header, the server knows how to read the body of the post request.
In the Mechanize#post() method, the second parameter is 'query', and the mechanize docs say this about the query argument:
The query is specified by either a string, or
a list of key-value pairs represented by a hash, or
an array of arrays.
http://rubydoc.info/gems/mechanize/Mechanize#post-instance_method
If you want to use the secret code named x-www-form-urlencoded in the body of your Mechanize#post() request, then you can provide a Hash with name/value pairs, e.g.
my_hash = {
'data' => '{"key1":"value1","key2":"value2"}'
}
Then you call Mechanize#post() like this:
my_agent.post(
'http://target_site.com',
my_hash,
{'Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8'},
)
Then Mechanize will convert the 'query' Hash into a String using the secret code named x-www-form-urlencoded and insert the string into the body of the post request. On the server side, the application that receives the post request can retrieve the json string doing something like this:
json_str = post_variables['data']
You should be aware that there are other secret codes that can be used for the body of a post request. One of them is called json, which is a string formatted using javascript syntax, for example:
'{
"id": 1,
"name": "A green door",
"price": 12.50,
"tags": ["home", "green"]
}'
Note how there are no '=' signs or '&' symbols in the json format--as there are with the x-www-form-urlencoded format, so the json secret code is much different from the x-www-form-urlencoded secret code.
If you want to use the json secret code in the body of your post request, you need to change two things when you call Mechanize#post(url, query, headers):
Provide a String for the 'query' argument.
Tell the server that the body of the post request uses the json secret code.
Like this:
json_str = '{"key1":"value1","key2":"value2"}'
my_agent.post(
'http://target_site.com',
json_str,
{'Content-Type' => 'application/json'},
)
When you pass a String argument for the query parameter, Mechanize doesn't do any processing of the String before inserting the String into the body of the post request. On the server side, the application that receives the post request can retrieve the json string by doing something like this:
json_str = request.body.read
#Then probably:
hash = JSON.parse(json_str)
The one hitch is that the server can ignore the Content-Type header and try to read the body of the post request using a secret code that it has already decided upon. If the body of your post request is not written in the secret code that the server expects, then you will get an error.
Note that the 'data' string you posted isn't valid json because it uses single quotes around the properties and values.

MultiJson::LoadError: 795: unexpected token when trying to parse incoming body request

I'm losing my sanity trying to parse an incoming request on a Sinatra app.
This is my spec
payload = File.read("./spec/support/fixtures/payload.json")
post "/api/v1/verify_payload", { :payload => payload }
last_response.body.must_equal payload
where is simply spec/support/fixtures/payload.json
{"ref":"refs/heads/master"}
My route looks like
post '/verify_payload' do
params = MultiJson.load(request.body.read, symbolize_keys: true)
params[:payload]
end
And running the spec I get the following error:
MultiJson::LoadError: 795: unexpected token at 'payload=%7B%22ref%22%3A%22refs%2Fheads%2Fmaster%22%7D'
I have tried to parse the body request in different ways without luck.
How can I make the request valid JSON?
THANKS
If you want to send a JSON-encoded POST body, you have to set the Content-Type header to application/json. With Rack::Test, you should be able to do this:
post "/api/v1/verify_payload", payload, 'CONTENT_TYPE' => 'application/json'
Alternatively:
header 'Content-Type' => 'application/json'
post '/api/v1/verify_payload'
More info here: http://www.sinatrarb.com/testing.html
The problem it is that you are passing a ruby hash, that is not well formated, you should pass a json object.
Something like this, should work:
post "/api/v1/verify_payload", { :payload => payload.to_json }

Google OAuth 2.0 returns invalid redirect_uri_mismatch when getting access_token

I am trying to exchange an OAuth one-time use code that I got from my client-side app into a access token and refresh token on my server. The response that I get is:
{
"error" : "redirect_uri_mismatch"
}
My POST request is:
POST /o/oauth2/token HTTP/1.1
Host: accounts.google.com
Content-Type: application/x-www-form-urlencoded
code={My Code}&
client_id={My Client ID}&
client_secret={My Client Secret}&
grant_type=authorization_code
I have checked my Client ID and Client Secret against those in the API Console and they match.
I get the one-time use code on my client with the following Java code:
static final List<String> SCOPES = Arrays.asList(new String[]{"https://www.googleapis.com/auth/plus.login","https://www.googleapis.com/auth/userinfo.email"});
String scope = String.format("oauth2:server:client_id:%s:api_scope:%s", SERVER_CLIENT_ID, TextUtils.join(" ", SCOPES));
final String token = GoogleAuthUtil.getToken(c, email, scope);
I have a redirect_uri in my API Console, but since I am trying to use cross-client authorization (as described here), I deliberately left it out of the POST request as is required:
When it exchanges the code for tokens, it should not include the “redirect_uri” argument in the POST.
Any idea on what I am doing wrong?
It turns out that "should not include the 'redirect_uri' argument in the POST" does not mean to completely omit the redirect_uri field. It instead means that the redirect_uri field should have an empty value.
My new, working POST is:
POST /o/oauth2/token HTTP/1.1
Host: accounts.google.com
Content-Type: application/x-www-form-urlencoded
code={My Code}&
client_id={My Client ID}&
client_secret={My Client Secret}&
redirect_uri=''&
grant_type=authorization_code
In my case the following - https://stackoverflow.com/a/25184995/775359 - answer was helpful:
var post_data = {
code : code,
client_id : google_client_id,
client_secret : google_client_secret,
redirect_uri : 'postmessage',
grant_type : grant_type,
};
Setting redirect_uri to postmessage solved my issue with redirect_uri mismatch.
EDIT: Another answer referencing postmessage: https://stackoverflow.com/a/18990268/775359

Resources