Bad URI with Ruby - ruby-on-rails

I am making an Oauth call to the Facebook API to get myself an access_token
def access_token
token_uri = URI("https://graph.facebook.com/oauth/access_token?client_id=#{CLIENT_ID}&client_secret=#{CLIENT_SECRET}&grant_type=client_credentials")
token_response = HTTParty.get(token_uri)
Rails.logger.info(token_response)
return token_response
end
I get a response and a access_token generated, let's say it's
access_token=123456789|abcdefghijk
but when I then try to use this token
def get_feed
fb_access_token = access_token
uri = URI("https://graph.facebook.com/#{VANDALS_ID}/posts/?#{fb_access_token}")
end
I get an error
URI::InvalidURIError: bad URI(is not URI?)
and the uri generated stops at the | even though there are more characters after the pipe to complete my access_key
https://graph.facebook.com/id-here/posts/?access_token=123456789|
How do I get the full access token available in my URI?

The reason you are getting an error is that | symbol is not allowed in the proper URI, hence it needs to be escaped before it's parsed. URI comes with a method to do it for you:
uri = URI(URI.escape "https://graph.facebook.com/#{VANDALS_ID}/posts/?#{fb_access_token}")
uri.to_s #=> https://graph.facebook.com/id-here/posts/?access_token=123456789%7Cabcdefghijk
When the url is requested, the server should automatically decode it, so all should work as expected.

Related

Rails API 422 Unprocessable Entity: No verification key available, heroku

I created a Rails API with a JWT authentication system and deployed it to Heroku. When I request the endpoints locally, all seems to be working fine but when I make requests to the live endpoints (i.e the Heroku deployed app) I get a: 422 Unprocessable Entity server error and the response body looks like this:
{
"message": "No verification key available"
}
The class responsible for encoding and decoding the auth token is defined as follows:
class JsonWebToken
# secret to encode and decode token
HMAC_SECRET = Rails.application.secrets.secret_key_base
def self.encode(payload, exp = 24.hours.from_now)
# set expiry to 24 hours from the creation time.
payload[:exp] = exp.to_i
# sign token with application secret
JWT.encode(payload, HMAC_SECRET)
end
def self.decode(token)
# get payload, first index in decoded Array
body = JWT.decode(token, HMAC_SECRET)[0]
HashWithIndifferentAccess.new body
# rescue from all decode errors
rescue JWT::DecodeError => e
# raise custom error to be handled by custom handler
raise ExceptionHandler::InvalidToken, e.message
end
end
I have an endpoint /signup where I can make a POST request to register a new user and POST /todos which is accessible and available only to registered users. Making a registration request works perfectly fine, but when I try to make the POST request to the /todos endpoint it raises an error.
The association between user and suit is 1:m respectively.
Please if you have any idea on how I can fix this, I'll be very grateful, thanks : ).
I finally figured a way out by altering the Rails.application.secrets.secret_key_base to Rails.application.secret_key_base. For a more detailed review on this please check out this link. Hopefully, this will help someone facing a similar issue.
This was also my problem. After checking out my json_web_token.rb file, I figured out that I had written the following line:
HMAC_SECRET = Rails.application.secrets.secret_key_base
There is an extra secrets reference, which is causing the problem. It should be:
HMAC_SECRET = Rails.application.secret_key_base
But as far as I'm concerned, you managed to figure it out yourself!

Issue making JWT token for use with Rails and rest-client gem

I'm new to Rails and I'm using rest-client to make outbound requests. It's easy to get successful results for a simple call like this:
#obj = RestClient.get 'https://jsonplaceholder.typicode.com/posts/1'
I need to hit a real endpoint and send a header with a jwt token (using ruby-jwt). According to the JWT readme, the content of the header should look like this:
Authorization: Bearer <token>
So I have some code to use a secret to make that token (and I confirmed the resulting token is valid) and put it into a headers variable, but I'm unsure about the syntax on that headers line, and whether it's right to use strings:
def build_headers (secret)
jwt_token = make_signed_JWT_token(secret)
headers = ("Authorization: Bearer "+ jwt_token)
return headers
end
Running it produces a 'headers' value like this:
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIxMTc3MjF9.dP2k1oPwjna5HdrnFeSqiVfR0Fz6J1ZlupfXMsPtFKw
I include that in my rest-client invocation like so:
#obj = RestClient.get(path, headers)
I'm no longer get a 401 Unauthorized error, but no celebrating yet; we appear to jump out of the code block at that line, with this exception:
undefined method `delete_if' for #<String:0x007f9e4de410b8>
Looking in the rest-client code, there is exactly one block that uses delete_if, whose purpose is to find and extract/remove "params" key if the value is a Hash/ParamsArray:
headers.delete_if do |key, value|
if key.to_s.downcase == 'params' &&
(value.is_a?(Hash) || value.is_a?(RestClient::ParamsArray))
if url_params
raise ArgumentError.new("Multiple 'params' options passed")
end
url_params = value
true
else
false
end
So my error suggests that it found something in this forbidden format and is trying to delete it, but that delete method isn't defined to act on a String. My best hunch is that there's something the matter with that headers item I created, but reading around I'm not finding more clues. Has anyone seen this before, or know if my jwt/header should be different?
You are passing a string where a hash is expected. Try this:
def build_headers (secret)
jwt_token = make_signed_JWT_token(secret)
headers = { authorization: "Bearer "+ jwt_token }
return headers
end

Issues sending a POST via Net::HTTP to a Battle.Net Community API End-Point (OAuth 2)

Versions:
Ruby 2.2.4
Rails 4.2
Omniauth-oauth2 1.3.1
Omniauth-bnet 1.1.0
Issue:
Trying to complete the authorization and token request process to Blizzard's Battle Net Community API. While I can get the authorization_code returned, when I attempt to construct a POST back to the token endpoint it keeps telling me that its an invalid request/internal server error or just returns back the following object: <Net::HTTPFound 302 Found readbody=true> which has a blank string for a response body. Details for how Blizzard recommends handling the OAuth 2 process are located here: Battle.net OAuth 2 Guide. The omniauth-bnet gem is the one Blizzard suggested but doesn't seem to handle the entire OAuth authorization and token process but I'll freely admit I'm brand new when it comes to anything OAuth related so I could be wrong.
Any help you all can provide would be very welcome!
Controller Code:
def index
client_id = ENV[BNET_CLIENT_ID]
client_secret = ENV[BNECT_CLIENT_SECRET]
uri = URI('https://us.battle.net/auth/token?
redirect_uri=https%3A%2F%2f127%2f0%2f0%2f1%3A3001%2Fauth%2Fbnet%2Fcallback
&grant_type=authorization_code
&code=' + params["code"])
req = Net::HTTP::Post.new(uri)
req.basic_auth(client_id, client_secret)
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') {|http| http.request(req)}
#bnet_response = res.body
end
Process:
Click link on index.html page which triggers the authorization process to start by sending a GET request to https://us.battle.net/oauth/authorize?client_id=&client_secret=&redirect_uri=&response_type=code
BNet API accepts the request and redirects user to Battle.Net Login screen
User authorizes app and is redirected (via redirect_uri) back to the index page with query parameters for the authorization_code
My app SHOULD make a POST request using the controller code above and the response should contain the actual access token.
This is just some hastily cobbled together code while I'm trying to get this working. I'll clean it up once I get over this little speed bump. I wouldn't hardcode the URI and such normally, just getting frustrated with things.
Things I've tried:
Using Rest-Client : There's a callback redirect that it can't handle apparently
Testing Postman : When using their OAuth 2 Authorization it works just fine, also works fine if I use Postman to get the authorization code (GET) and token (POST), so I'm assuming things work on the Blizz side and my code just sucks.
config/routes.rb
Rails.application.routes.draw do
root to: 'pages#index'
get '/auth/:provider/callback', to: 'pages#index'
end
I'm not familiar with the Battle.net API, however reading your process it seems you are getting an authorization code back from the redirect in the form of a query parameter (accessed by params[:code]) correct?
You also mention that this works using Postman indicating the flaw must be somewhere in the code.
I would suggest using the following snippet and let me know if it works. I would encourage using puts url after url = ... code to ensure the URI looks exactly as you want it (and has the correct code, client id, secret, redirect_uri).
require 'uri'
require 'net/http'
# Remember to change the redirect uri to match yours
redirect_uri = 'https://my-domain-name.com/auth/bnet/callback'
# Double check the environment variable names for BNET ID / Secret
url = URI("https://us.battle.net/oauth/token?redirect_uri=#{redirect_uri}&code=#{params[:code]}" \
"&grant_type=authorization_code&scope=wow.profile&client_id=#{ENV['BNET_CLIENT_ID']}" \
"&client_secret=#{ENV['BNET_CLIENT_SECRET']}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_body
Ensure your domain is accessible over https as this seems to be required.

Handling ENV Variables in api request

I am making a request to the facebook graph api and have saved my USER_ID and ACCESS_TOKEN in ENV variables. I'm wondering if this is a best practice as this morning I am encountering a URI error that I was not getting yesterday.
class FacebookFeed
#Constants
VANDALS_ID = ENV['VANDALS_FB_ID']
FB_ACCESS_TOKEN = ENV['FACEBOOK_ACCESS_TOKEN']
FACEBOOK_URL = 'https://graph.facebook.com/"#{VANDALS_ID}"/posts/?access_token="#{FB_ACCESS_TOKEN}"'
def get_feed
uri = URI(FACEBOOK_URL)
response = HTTParty.get(uri)
results = JSON.parse(response.body)['data']
puts results
end
end
So in the Rails console I am just trying to get a response but am getting:
URI::InvalidURIError: bad URI(is not URI?): https://graph.facebook.com/"#{VANDALS_ID}"/posts/?access_token="#{FB_ACCESS_TOKEN}"
This is strange as this was working yesterday. Is there anything I am missing or is there a better way to store my User_ID and Access Token?
Update
When doing a 'puts uri' this is returned:
https://graph.facebook.com/%22%23%7BVANDALS_ID%7D%22/posts/?access_token=%22%23%7BFB_ACCESS_TOKEN%7D%22
I assume this is what is being sent as the GET request, because when I then do 'puts response' I get:
{"error":{"message":"Invalid OAuth access token.","type":"OAuthException","code":190}}
How do I construct the request correctly?
After talking to Rich on Skype, the problem was resolved with Sergey Kishenin's comment:
FACEBOOK_URL = "https://graph.facebook.com/#{VANDALS_ID}/posts/?access_token=#{FB_ACCESS_TOKEN}"

Authenticating to an API with a token

I'm working with the Zendesk API, an HTTPS-only, JSON API and authentication is required to update a resource, and the API supplies an API token to use when authenticating as different users. When updating a resource, I issue a PUT request over SSL, assign the appropriate JSON content to the request body, and specify the Content-Type request header as application/json.
Next, the API instructs its users to authenticate as the end-user by either using the user's email and password (which I can't do for several reasons) or to use the user's email address along with the API token. The following is my attempt to authorize to the API with the Authorization header:
#id = params[:id]
#comment_body = params[:comment]
uri = URI.parse "https://{subdomain}.zendesk.com/api/v2/requests/#{#id}.json"
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
req = Net::HTTP::Put.new(uri.request_uri)
req.body = '{"request": {"comment":{"value":' + "\"#{#comment_body}\"" + '}}}'
req['Content-Type'] = 'application/json'
#The following two lines do not work!
credentials = Base64::encode64("{user_email}/token:{api_token}")
request.headers['Authorization'] = "Basic #{credentials}"
response = http.request(req)
The API specifies that the format for authentication using the API token is {user_email}/token:{api_token}. I encoded that format with Base64::encode64 and passed it to the Authorization Header preceded with Basic, but the response is a 401 Unauthorized. However, replacing those two lines with req.basic_auth {user_email}, {user_password} works fine.
So my question is, how can I authenticate as a different user using the email and the given API token as authentication instead of supplying the user's email and password to req.basic_auth?
The googling I've done on the topic has revealed very little; apparently it's a lot more common to use the normal {username}:{password} format when dealing with the Authorization header than an API token.
Thanks in advance!!
Update: Weirdly, trying to authenticate as the end-user with req['Authorization'] = "Basic #{credentials}" does not return a 401 Unauthorized Error or a WWW-Authenticate header while trying to authorize as request.headers['Authorize'] = "Basic #{credentials}" does.
Finally figured it out after much head-banging and nearly throwing my laptop out the window. Suddenly, the answer seems incredibly obvious.
When using Net::HTTP, its basic_auth method can also accept tokens depending on the API, and the Zendesk API specifies that the format for using the API token is {email}/token:{token}. Basic authentication uses the format {username}:{password}, where the two fields are separated by a colon, meaning in Zendesk's case I can place {email}/token as the first argument and {token} as the second argument (instead of the username as the first argument and the password as the second argument), so the following code is correct:
req.basic_auth "{email}/token", "{api_token}"
I hope anyone who found this useful could leave a comment. Nice to know I spared someone from this frustration.

Resources