Passing cookies with HTTParty - ruby-on-rails

I'm trying to log in a user with HTTParty into a Rails application.
The idea is to send a POST request and get a session cookie with it, then send a GET request with this cookie and log in successfully:
POST request
HTTParty.post('url/login/', basic_auth: {user: 'user', password: 'password'}, follow_redirects: false)
returns
#<HTTParty::Response:0x7f9c71bc4598 parsed_response=nil, #response=#<Net::HTTPFound 302 302 readbody=true>, #headers={"date"=>["Mon, 04 Mar 2019 08:02:26 GMT"], "server"=>["Apache"], "strict-transport-security"=>["max-age=31536000"], "set-cookie"=>["JSESSIONID=6552C1F4FD12D1C5B1D3B42168B9588A.node2; Path=/; Secure; HttpOnly"], "x-content-type-options"=>["nosniff"], "x-xss-protection"=>["1; mode=block"], "cache-control"=>["no-cache, no-store, max-age=0, must-revalidate"], "pragma"=>["no-cache"], "expires"=>["0"], "location"=>["/ipad-api/v20/login/failure/"], "vary"=>["Accept-Encoding,User-Agent"], "content-length"=>["20"], "connection"=>["close"], "content-type"=>["text/plain; charset=UTF-8"]}>
The I send a GET request
HTTParty.get('url/login/success/', cookie: "6552C1F4FD12D1C5B1D3B42168B9588A.node2")
and get
#<HTTParty::Response:0x7f9c71b95950 parsed_response={"head"=>{"apikey"=>nil, "sessionid"=>"320E4C622043566D5424627BDE11997D.node3", "timestamp"=>1551686567666, "sessiontimeout"=>1551689267666, "wishlistItemsCount"=>0, "basketItemsCount"=>0, "loggedIn"=>false, "role"=>"G"}, "data"=>{"user"=>{"profile"=>{"title"=>nil, "firstname"=>nil, "lastname"=>nil, "street"=>nil, "street2"=>nil, "postalcode"=>nil, "city"=>nil, "customerID"=>nil, "customerType"=>0}}, "abandonedBasket"=>false}, "messages"=>[{"code"=>"bmd.api.login.success", "statusCode"=>200, "description"=>"OK"}]}, #response=#<Net::HTTPOK 200 200 readbody=true>, #headers={"date"=>["Mon, 04 Mar 2019 08:02:47 GMT"], "server"=>["Apache"], "strict-transport-security"=>["max-age=31536000"], "set-cookie"=>["JSESSIONID=320E4C622043566D5424627BDE11997D.node3; Path=/; Secure; HttpOnly"], "x-content-type-options"=>["nosniff"], "x-xss-protection"=>["1; mode=block"], "cache-control"=>["no-cache, no-store, max-age=0, must-revalidate"], "pragma"=>["no-cache"], "expires"=>["0"], "vary"=>["Accept-Encoding,User-Agent"], "connection"=>["close"], "transfer-encoding"=>["chunked"], "content-type"=>["application/json;charset=UTF-8"]}>
Session changes and the user isn't logged in. Same requests with curl log a user in successfully.
Research showed that it might be not easy and this solution doesn't work either.
Any ideas what I'm doing wrong and in what direction to think? Change to faraday, as suggested here?

To login using HTTParty you have to look more things than cookies. You have to see CSRF_token too. I think you can get authenticity_token using gsub method but I tried and it was quite difficult to create regex. So I used Nokogiri to get token which is actually present in the sign in form. Following is details and at the end, I will put the whole code.
Adding required Gems, you can add it in Gemfile
gem 'httparty'
gem 'nokogiri'
Run bundle install to get gem installed.
To get CSRF_token we have to get sign_in page.
url = "http://localhost:3000/users/sign_in"
get_response = HTTParty.get(url)
noko_doc = Nokogiri::HTML(get_response)
auth_token = noko_doc.css('form').css('input[name="authenticity_token"]').first.values[2]
This way we got auth_token which was in the form as a hidden field. Now let us get cookies as session cookie may needed.
cookie_hash = HTTParty::CookieHash.new
get_response.get_fields('Set-Cookie').each { |c| cookie_hash.add_cookies(c) }
Here we are getting cookies where session is also present.
Now it is time to get final params and than we will send both cookies and session to login
params = {"utf8" => "✓", "authenticity_token" => auth_token, "user[email]"=>"user#email.com",·
"user[password]"=>"password"}
params["commit"] = "Login"
Now params are ready, you can use following httparty request to login and get cookies.
response = HTTParty.post("http://localhost:3000/users/sign_in", {:body=>params, headers: {'Cookie' => cookie_hash.to_cookie_string }} )
Now for other request you can run same cookies method to get all cookies back
cookie_hash = HTTParty::CookieHash.new
get_response.get_fields('Set-Cookie').each { |c| cookie_hash.add_cookies(c) }
And to access other pages you can send request with cookies as we did in above example. Remember if you again going to use any page which has form, again you need to get its csrf too.
response = HTTParty.post("http://localhost:3000/users/other_urls", {headers: {'Cookie' => cookie_hash.to_cookie_string }} )
I tried this code and it is working perfectly. Here is complete code for your use
require 'httparty'
require 'Nokogiri'
require 'Pry'
url = "http://localhost:3000/users/sign_in"
get_response = HTTParty.get(url)
noko_doc = Nokogiri::HTML(get_response)
auth_token = noko_doc.css('form').css('input[name="authenticity_token"]').first.values[2]
cookie_hash = HTTParty::CookieHash.new
get_response.get_fields('Set-Cookie').each { |c| cookie_hash.add_cookies(c) }
params = {"utf8" => "✓", "authenticity_token" => auth_token, "user[email]"=>"user#example.com",·
"user[password]"=>"password"}
params["commit"] = "Login"
response = HTTParty.post("http://localhost:3000/users/sign_in", {:body=>params, headers: {'Cookie' => cookie_hash.to_cookie_string }} )
puts response

Related

Issue with JSON request - rails/faraday

I have an issue with rails and faraday when I try to receive access token which should be included in a JSON response from the external Api.
What I want to do is user authentication based on external API.
I assume that the User already has valid credentials (in this case email as username and password).
Now when he connects to my Api, I send JSON request to the external Api to verify whether this user is valid and wait for access token.
Once the access token is sent in a response, user authentication is successful and I have access to the other endpoints
This is my controller
module Api
class AuthenticationController < ApplicationController
def create
client = XXX::AuthClient.new
response = client.authenticate(
email: params[:email],
password: params[:password]
)
api_client = XXX::Client.new(response[:access_token])
if response[:access_token]
api_user = api_client.get_user()
if api_user["id"]
db_user = User.create(xxx_id: api_user["id"], xxx_access_token: response[:access_token])
end
end
render json: { access_token: db_user.access_token }
end
end
end
And this is my AuthClient service
class AuthClient
def initialize
#http_client = Faraday.new('https://auth.xxx.com/')
end
def authenticate(email:, password:)
headers = {
'Content-Type': 'application/json'
}.to_json
body = {
grant_type: "password",
username: email,
password: password,
client_id: "particularclientid",
client_secret: "particularclientsecret"
}.to_json
api_response = http_client.post("/oauth2/token", body)
response = JSON.parse(api_response.body)
if response["access_token"]
{ access_token: access_token }
else
{ error: "autentication error" }
end
end
private
attr_reader :http_client
end
end
What I know is that curl in the following format is correct and I can see User's access token, refresh token etc.
curl -X POST -H "Content-Type: application/json" -d '{
"grant_type": "password",
"username": "test+user#example.com",
"password": "examplepassword",
"client_id": "particularclientid",
"client_secret": "particularclientsecret"
}' "https://auth.xxx.com/oauth2/token"
But when I run my curl
curl -X POST -d 'email=test+user#example.com&password=examplepassword' "http://localhost:3000/api/auth"
I see that my request is not correct. But I have no clue where is the problem because header and body are formatted to JSON (I have entered puts headers, puts body and puts response to verify that).
Started POST "/api/auth" for 127.0.0.1 at 2017-03-31 16:42:26 +0200
Processing by Api::AuthenticationController#create as */*
Parameters: {"email"=>"test user#example.com", "password"=>"[FILTERED]"}
{"Content-Type":"application/json"}
{"grant_type":"password","username":"test user#example.com","password":"examplepassword","client_id":"particularclientid","client_secret":"particularclientsecret"}
{"error"=>"invalid_request", "error_description"=>"The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed."}
Completed 500 Internal Server Error in 610ms (ActiveRecord: 0.0ms)
NoMethodError (undefined method `access_token' for nil:NilClass):
app/controllers/api/authentication_controller.rb:21:in `create'
Is my request incorrect or the problem exists somewhere else?
I am not experienced developer. Just trying to learn enough to start as a Junior RoR. I tried to find a solution on stack and on different sites but I am stucked. Even faraday docs does not help me much
When URIs are escaped, a + is used as a replacement for whitespace. As such, when your controller un-escapes the URI, the + is changed back into a space. If you want to send a space, use %2B instead.
For your first problem, the error message indicates that db_user is nil when you try to do db_user.access_token. So, either response[:access_token] is nil, api_user["id"] is nil, or User.create failed.
You'll need to put in some debugging to find out where your problem is.

How to successfully use validate_token in the devise_token_auth gem?

I am fairly new to Ruby and Rails itself and I'm trying to build a simple Rails API.
I want to authenticate users via token and I am therefore using the devise_token_auth gem.
I can successfully make a POST request at /auth/sign_in and I am now trying to make a GET request at /auth/validate_token
What I have, as a "test":
uri = URI.parse("http://localhost:3000/auth/sign_in")
response = Net::HTTP.post_form(uri, {"email" => params[:session][:email], "password" => params[:session][:password]})
uri2 = URI.parse("http://localhost:3000/auth/validate_token")
params = { :auth_token => response['access-token'], :uid => response['uid'], :client => response['client'] }
uri2.query = URI.encode_www_form(params)
response2 = Net::HTTP.get_response(uri2)
I am therefore using the access-token and uid retrieved from the response header but I keep getting a 401 response code from /auth/validate_token:
Started GET "/auth/validate_token?auth_token=EEV40VDHfOaWtBzv3bn_DQ&uid=username%40example.com&client=NAzWNJalYBJLRni9dCGxXA" for ::1 at 2016-06-22 15:22:35 +0100
Processing by DeviseTokenAuth::TokenValidationsController#validate_token as */*
Parameters: {"auth_token"=>"EEV40VDHfOaWtBzv3bn_DQ", "uid"=>"username#example.com", "client"=>"NAzWNJalYBJLRni9dCGxXA"}
Completed 401 Unauthorized in 0ms (Views: 0.1ms | ActiveRecord: 0.0ms)
What am I doing wrong? How can I solve this?
I believe the problem is twofold:
you send the authentication credentials as headers to the /validate_token endpoint
you send the token header as access-token instead of auth_token
You can read about it in this github issue. It may not have been at the time of your problem, but it is currently published in the README.
Here are all the headers necessary for a valid authenticated request (at the time of this writing):
"access-token": "wwwww",
"token-type": "Bearer",
"client": "xxxxx",
"expiry": "yyyyy",
"uid": "zzzzz"
Note: these are not necessary for every endpoint, but usually access-token, client, and uid are.

Submitting a POST request from a rails controller

I have followed the answer of this question. And this is what i have.
def index
$response = get_insta_feed
end
def get_insta_feed
require "net/http"
require 'net/https'
require "uri"
$tag = "test"
uri = URI.parse("https://api.instagram.com/v1/tags/"+$tag+"/media/recent")
uri.query = URI.encode_www_form(parameters)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new(uri.request_uri)
http.request(request).to_json
end
private
def parameters
{
"access_token" => "my access token here"
}
end
on my view page i just want to display the full json response first before parsing the data that i want to display so this:
<div>
<%=$response%>
</div>
and this is what is displayed in my div:
{"server":["nginx"],"date":["Fri, 07 Nov 2014 06:13:34 GMT"],"content-type":["text/html; charset=utf-8"],"allow":["GET"],"content-language":["en"],"expires":["Sat, 01 Jan 2000 00:00:00 GMT"],"vary":["Cookie, Accept-Language"],"pragma":["no-cache"],"cache-control":["private, no-cache, no-store, must-revalidate"],"set-cookie":["csrftoken=e70981e518d478dd7362049f9ce89cc9; expires=Fri, 06-Nov-2015 06:13:34 GMT; Max-Age=31449600; Path=/","ccode=PH; Path=/"],"connection":["close"]}
What am I doing wrong?
Please use instagram gem for the connection tasks to Instagram. For example you can use it as follows:
client = Instagram.client(:access_token => session[:access_token])
for media_item in client.tag_recent_media(tag_name)
# Use the folowing fields...
# media_item.images.thumbnail.url
# media_item.id
# media_item.likes[:count]
end
For more information of the gem, please refer to its github page.

Spotify Web API Bad Request Error "invalid_client" when refreshing token

I'm building an app in Rails using the Spotify web API. I built a method to refresh a user's token, but am receiving a 400 error. According the the Spotify Web API docs, the header of my request needs to be in the following format:
Authorization: Basic <base64 encoded client_id:client_secret>
Using Httparty gem, here's the POST method to refresh the access token:
def refresh_token
client_id = "foo"
client_secret = "bar"
client_id_and_secret = Base64.encode64("#{client_id}:#{client_secret}")
result = HTTParty.post(
"https://accounts.spotify.com/api/token",
:body => {:grant_type => "refresh_token",
:refresh_token => "#{self.oauth_refresh_token}"},
:headers => {"Authorization" => "Basic #{client_id_and_secret}"}
)
end
Here's what "result" ends up being:
=> #<HTTParty::Response:0x7f92190b2978 parsed_response={"error"=>"invalid_client", "error_description"=>"Invalid client secret"}, #response=#<Net::HTTPBadRequest 400 Bad Request readbody=true>, #headers={"server"=>["nginx"], "date"=>["Sun, 31 Aug 2014 22:28:38 GMT"], "content-type"=>["application/json"], "content-length"=>["70"], "connection"=>["close"]}>
I can decode client_id_and_secret and it returns "foo:bar", so I'm at a loss as to why I'm receiving a 400 error. Any insight is much appreciated.
Found the issue... it was with the Base64 encoding in Ruby. Apparently (as shown in Strange \n in base64 encoded string in Ruby) using the Base64.encode64('') method adds an extra line within the code. Using Base64.strict_encode64('') solved the issue.
Updated code:
def refresh_token
client_id = "foo"
client_secret = "bar"
client_id_and_secret = Base64.strict_encode64("#{client_id}:#{client_secret}")
result = HTTParty.post(
"https://accounts.spotify.com/api/token",
:body => {:grant_type => "refresh_token",
:refresh_token => "#{self.oauth_refresh_token}"},
:headers => {"Authorization" => "Basic #{client_id_and_secret}"}
)
end

Deleting Cookies from a Controller

I set some cookie values in my form using jQuery. I can read them just fine in my Rails controller via the cookies method. When I call cookies.delete(:my_key), they appear to be gone when I call cookies again. But when I reload the page, the cookies are back again.
Is there a way to delete the cookies for good from inside my controller?
EDIT
This is very strange since I'm looking at the response headers and they seem to be deleting the cookie. Maybe it's because it is a 302 request?
Set-Cookie: my_key=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT
For example, your cookie look like this
cookies[:foo] = {:value => 'bar', :domain => '.text.com'}
As you tried this one => cookies.delete :foo
The logs will say => Cookie set: foo=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT
Notice that the domain is missing. Tried this way
cookies.delete :foo, :domain =>
'.text.com'
Function = >
# Removes the cookie on the client machine by setting the value to an empty string
# and setting its expiration date into the past. Like []=, you can pass in an options
# hash to delete cookies with extra data such as a +path+.
def delete(name, options = {})
options.stringify_keys!
set_cookie(options.merge("name" => name.to_s, "value" => "", "expires" => Time.at(0)))
end
According to the rails api, there is now a delete method, so if you have not set the domain use
cookies.delete :my_key
and if you have set the domain
cookies.delete :my_key, domain: 'mydomain.com'
We can delete the cookies by passing name and options to delete method as follows:
Syntax: delete(name, options = {})
Description: Removes the cookie on the client machine by setting the value to an empty string and the expiration date in the past. Like []=, you can pass in an options hash to delete cookies with extra data such as a :path.
Example:
cookies.delete('JWT', {
value: "",
expires: 1.day.ago,
path: '/api'
})

Resources