Spotify authorization_code - ruby

I'm trying to allow my app to log into Spotify via their Authorization Code Flow. I am able to receive an authorization code from Spotify in the initial authorization step, but receive the following error when trying to get an access token:
{"error":"invalid_client","error_description":"Invalid client"}
My code is as follows:
# Callback from Spotify Authorization
get '/auth/spotify/callback' do
session[:code] = params[:code]
redirect to '/refresh'
end
Then, I am POSTing the following:
get '/refresh' do
uri = URI('https://accounts.spotify.com/api/token')
resp = Net::HTTP.post(uri,
{
"grant_type" => "authorization_code",
"code" => session[:code].to_s,
"redirect_uri" => "http://localhost:4567/auth/spotify/callback",
"client_id" => client_id,
"client_secret" => client_secret
}.to_json
)
"#{resp.body}"
end
Any help would be appreciated
EDIT: I've also tried to POST the same parameters above using PostMan, but receive the same error message

You need to add the Authorization header in your POST request.
Add the following key in your Net::HTTP.post options :
{'Authorization' => 'Basic YOUR_AUTH_CODE' }
EDIT :
This is in the docs under the 'Your application requests refresh and access tokens' heading.

To answer my own question:
I didn't need to make the request since the gem I was using, 'omniauth-spotify', could return the access token to me in request.env['omniauth.auth'].credentials.token
I was also creating the POST request incorrectly. The example below is the correct way to make a POST and obtain a new token from a refresh_token (provided in the above .credentials hash)
# Get new access token from refresh token
# session[:creds] = request.env['omniauth.auth'].credentials
get '/refresh' do
refresh_token = session[:creds].refresh_token
auth = "Basic " + Base64.strict_encode64("#{client_id}:#{client_secret}")
uri = URI.parse('https://accounts.spotify.com/api/token')
request = Net::HTTP::Post.new(uri)
request["Authorization"] = auth
request.set_form_data(
"grant_type" => "refresh_token",
"refresh_token" => refresh_token,
)
req_options = {
use_ssl: uri.scheme == "https",
}
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
http.request(request)
end
"#{response.code}" # > 200 OK
end

Related

Ruby implementing oauth2 client credentials flow

I'm new to Ruby and I'm trying to implement a oauth2 with client credentials flow.
I've found the "ouath2" gem, but that requires a redirect_uri that I don't have.
Here is the gem.
Here is what I'm trying to implement
secret_id = 'this-is-a-secret-id'
token_id = 'this-is-a-token-id'
scope = 'such-a-good-scope'
grant_type = 'client_credentials'
#client = nil
# Get access token
def GetAccessToken
HttpRequest request = HttpRequest::Post("https://awesome-page.com/oauth/token")
request.content = {
{ "client_id" => token_id },
{ "client_secret" => secret_id }
{ 'grant_type' => grant_type },
{ 'scope' => scope}
}
response = request.send
json = response.content
accessToken = JsonConvert.DeserializeObject<Token>(json)
#client = Client.new(bearer: accessToken)
end
# Refresh token
def RefreshToken
HttpRequest request = HttpRequest::Post("https://awesome-page.com/oauth/token")
request.content = {
{ "client_id" => token_id },
{ "client_secret" => secret_id }
{ 'grant_type' => grant_type },
{ 'refresh_token' => scope}
}
response = request.send
json = response.content
accessToken = JsonConvert.DeserializeObject<Token>(json)
#client = Client.new(bearer: accessToken)
end
# End then implementing the "getting the resources with the client" part and so on...
Any idea how to do this, I'm getting a little bit desperate now
Any help is greatly appreciated!
You’re kind of going about this all wrong. With the Oauth2 gem you need to:
Initialise a new Oauth2::Client with your client ID, secret, scope, and define the token and redirect url (this is a url in your app that users logging in get sent back to. Not used for Client Credentials flow as it’s for server to server comms)
Call token = client.client_credentials.get_token. This sets token to an AccessToken it obtained.
Then call token.get(‘https://your-url.com/path/to/resource’) - or post/patch/delete.
Look at the access_token.rb file in the repo to see the methods you can call. They also take a series of params, for things like additional headers or body payload you can pass. It’s based on Faraday so you can always look up Faraday docs for help with that part.

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...

Google Signin from server side app in 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)

In ruby with sinatra, How to get I response with get method on rest client?

I use ruby with sinatra and I used rest-client on import for payment.
I got token that string typed through post method on specific url: '... /users/getToken'.
Using this token, I wanna get payments information with get method on this url:
get_url = 'https://api/iamport.kr/payments/'+imp_uid
the detail codes are below,
def get_paymentsdetails(token, imp_uid)
get_url = 'https://api.iamport.kr/payments/'+imp_uid
response = RestClient.get get_url, :data => {}.to_json, :accept => :json, :headers => {'Authorization' => token}
json = JSON.parse(response, :symbolize_names => true)
# json = JSON.parse(response.to_json, {:symbolize_names => true})
return json
end
However, I got 401 unauthorized error on this part of code.
response = RestClient.get get_url, :data => {}.to_json, :accept => :json, :headers => {'Authorization' => token}
After I access get_url with specific imp_uid, I got this page,{"code":-1,"message":"Unauthorized","response":null}
I checked parameter token and imp_uid of get_paymentsdetails function have valid string values,, so How can I access response parameter??
I think that there are some problems on response = RestClient.get get_url.... code.
Thanks.
Method 'get' from the 'RestClient' class return some object with attributes. So response have few values. Which of them do you need? Access to them you can get by their names, its described here.
In your case, after response = RestClient.get get_url... you should have variable response and ability to call response.headers, response.code or response.body.
But im afraid that you have some problems with autorization, which means that imp_uid or token is not correct. Thats why remote server sended to you responce with http-code 401 (Unauthorized). If it is so you should try to check your imp_uid and token. If everything is correct try to reach support of iamport.kr .

Can you make a YouTube analytics API request using Net::HTTP in Ruby?

Assuming my access_token and my TEST_CHANNEL_ID are correct, shouldn't this request work?
I'm getting a 404 error, am I missing some parameter?
uri = URI.parse("https://www.googleapis.com")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new("/youtube/analytics/v1/reports")
request.body = URI.encode_www_form({"Authorization" => "Bearer #{access_token}", "ids" => "channel==#{TEST_CHANNEL_ID}", "start-date" => "2015-07-01", "end-date" => "2015-07-20", "metrics" => "views"})
response = http.request(request)
It's worth mentioning that I obtained my access_token with a refresh_token authorized for all youtube api scopes
If you're passing the access token as a URL parameter, the name of the parameter should be "access_token" and you don't need the "Bearer" part of the string; it would just be:
request.body = URI.encode_www_form({"access_token" => "#{access_token}", "ids" => "channel==#{TEST_CHANNEL_ID}", "start-date" => "2015-07-01", "end-date" => "2015-07-20", "metrics" => "views"})
You only use the Authorization: Bearer {whatever}form if you're setting the access token in the request header.
Here's the documentation on this.

Resources