Google Signin from server side app in ruby - ruby

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)

Related

OAuth error in using twitter API v2 for posting a tweet

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

How to authenticate to the Confluence rest api using Atlassian jwt token in ruby

I am trying to make api calls to Confluence cloud by authenticating with jwt. In my descriptor file, I have opted for the read scope.
I am following this guide on how to generate the jwt token. In my code base, I have
require 'atlassian/jwt'
require 'json'
require 'httparty'
url = "https://my-domain.atlassian.net/wiki/rest/api/space/space_key"
issuer = 'my_issuer'
http_method = 'get'
shared_secret = 'shared_secret'
claim = Atlassian::Jwt.build_claims(issuer, url, http_method)
jwt = JWT.encode(claim, shared_secret)
response = HTTParty.get("https://my-domain.atlassian.net/wiki/rest/api/space/space_key", :headers => {'Content-Type' => 'application/json', authorization: jwt})
puts response

Post request to selling partner API sandbox endpoint return InvalidSignature

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

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?

Resources