How to successfully use validate_token in the devise_token_auth gem? - ruby-on-rails

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.

Related

401 Unauthorized error when making requests to rails api on heroku

I just deployed my first app to heroku and it used a Rails API (backend) and a React frontend. When I deployed my app I get 401 unauthorized errors whenever I try to make a request that involves the use of a JSON Web Token(JWT). I am sending the token from localstorage in my app and everything worked fine when I was sending it in my development environment. I only have this issue in production.
When I make the fetch request from my frontend and send over my JWT to my backend, I get the following messages in my heroku server logs:
2020-11-29T04:45:31.742735+00:00 app[web.1]: I, [2020-11-29T04:45:31.742670 #4] INFO -- : [f3c19eae-e431-4c9f-b93b-7499797f2c03] [active_model_serializers] Rendered ActiveModel::Serializer::Null with Hash (0.13ms)
2020-11-29T04:45:31.742984+00:00 app[web.1]: I, [2020-11-29T04:45:31.742919 #4] INFO -- : [f3c19eae-e431-4c9f-b93b-7499797f2c03] Filter chain halted as :authorized rendered or redirected
2020-11-29T04:45:31.744091+00:00 app[web.1]: I, [2020-11-29T04:45:31.744019 #4] INFO -- : [f3c19eae-e431-4c9f-b93b-7499797f2c03] Completed 401 Unauthorized in 2ms (Views: 0.7ms | Allocations: 218)
Another strange thing about this is that I get an error message along with the 401 status error in my React frontend that tells me to Please Log in even though I logged into my app to receive a token from my backend before I even attempted to make another fetch request
Below I will post other relevant code snippets so that you can see what I was trying to do
Here is the Code in my frontend that sends the request
addToCart = () =>{
//make a fetch request to add the item in the customer's current cart
fetch("https://health-and-fit-store-api.herokuapp.com/cart_products",{
method:"POST",
headers:{
"Authorization": localStorage.token,
"Content-Type":"application/json",
Accept:"application/json"
},
body:JSON.stringify({
productId :this.props.id
})
})
.then(res => res.json())
.then(data => {
this.props.addToCart(data)
toast.dark(`Added ${data.product.name.toLowerCase()} to your cart`)
})
}
Here is the code in my Rails API that receives the request
before_action :authorized, only: [:create,:delete]
def create
#will be receiving token in fetch request
#use the of the current cart and the product id passed in by the post request
current_cart = #customer.carts.find_by(checked_out:false)
product = Product.find(params[:productId])
new_cart_product = CartProduct.create(cart:current_cart,product:product, quantity:1)
render json: new_cart_product
end
Here is the code from my application controller that I used to set up JWT
class ApplicationController < ActionController::API
def encode_token(payload)
# should store secret in env variable
JWT.encode(payload, ENV['jwt_encode_string'])
#byebug
end
def auth_header
# { Authorization: 'Bearer <token>' }
request.headers['Authorization']
end
def decoded_token
if auth_header
token = auth_header
# header: { 'Authorization': '<token>' }
begin
JWT.decode(token, ENV['jwt_encode_string'], true, algorithm: 'HS256')
rescue JWT::DecodeError
nil
end
end
end
def logged_in_customer
if decoded_token
customer_id = decoded_token[0]['customer_id']
#customer = Customer.find_by(id: customer_id)
end
end
def logged_in?
!!logged_in_customer
end
def authorized
render json: { error_message: 'Please log in' }, status: :unauthorized unless logged_in?
end
end
If anyone can help me out with this, I would really appreciate it, I've been stuck on this for days. Also please note that I have checked out every other post involving this issue on StackOverflow and have exhausted every kind of search on Google that I could think of.
thanks to #snake, I actually ended up solving this issue. The problem wasn't with the token I was using, but their suggestion led me to go back and check out my fetch requests to the api endpoint and I had an extra comma at the end of the request that was causing the 401 status code error.
Once I fixed that, everything worked beautifully.

Rails Net::HTTP.post_form API returns 500 Internal Error although it returns correct response through PostMan

I am trying to get the API Token from this api.
https://docs.bigmarker.com/#login
Basically below is the API format and it works with PostMan as well
curl -i -X POST --data "email=youremail#email.com&password=yourpassword" https://www.bigmarker.com/api/v1/members/login
I didn't specify any Headers, Just use the POST form data with email & password.
And this is my rails code.
require 'net/http'
require 'net/https'
uri = URI("https://www.bigmarker.com/api/v1/members/login")
res = Net::HTTP.post_form(uri, {'email' => email, 'passowrd' => password})
But it returns HTTP 500 Error.
#<Net::HTTPInternalServerError 500 Internal Server Error readbody=true>
Any idea?
Thanks
My mistake.
There was a spelling error.
res = Net::HTTP.post_form(uri, {'email' => email, 'passowrd' => password})
'passowrd' should be 'password'

Passing cookies with HTTParty

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

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.

Can I send requests to app with devise without credentials?

I have backend app with devise and a ionic app from which I make requests to backend.
Whenever I send requests, gets me 401 status unauthorized.
In this project I've already doorkeeper to manage authorization, so I don't rly need authorization from devise. Can I somehow make these requests( add something to headers ) always authorized without sending post request with credentials?
You need to identify the user somehow. If you're not using a session, you'll need to generate some kind of access token, which Doorkeeper can do for you. I'm not sure if this is helpful or not, but I set up the following flow recently.
One option when using OAuth2 through a trusted client, e.g. a front-end app you build/distribute yourself, is the Resource Owner Password Credentials Grant. Doorkeeper has a guide in the docs for this with advice on dealing with Devise.
I ended up with something like this in my Doorkeeper initializer. You don't need to authorize the client, because you trust it:
resource_owner_from_credentials do |routes|
request.params[:user] = {:email => request.params[:email], :password => request.params[:password]}
request.env['devise.allow_params_authentication'] = true
request.env['warden'].authenticate!(:scope => :user)
end
skip_authorization do |resource_owner|
true
end
Then you should be able to send a request using the password grant type as follows (also shown in the docs).
RestClient.post 'http://localhost:3000/oauth/token', {grant_type: 'password', email: 'email#gmail.com', password: 'password'}, {:accept => 'application/json'}
You should receive the same JSON back as shown in the docs.
{
"access_token": "1f0af717251950dbd4d73154fdf0a474a5c5119adad999683f5b450c460726aa",
"token_type": "bearer",
"expires_in": 7200
}
Now you have a way of identifying your user. You just attach this token to each request to endpoints protected by doorkeeper_for.
RestClient.get 'http://localhost/api/v1/users', { 'Authorization' => 'Bearer 1f0af717251950dbd4d73154fdf0a474a5c5119adad999683f5b450c460726aa', :accept => 'application/json'}

Resources