I need some help with testing the following. I am doing the RailsCast about securing an api: http://railscasts.com/episodes/352-securing-an-api?view=asciicast
I have a RequestController with a before_filter to check if the request has a token:
class RequestsController < ApplicationController
include ActionController::MimeResponds
include ActionController::HttpAuthentication::Token::ControllerMethods
before_filter :restrict_access
respond_to :json
#...
def authenticate
return restrict_access
end
private
def restrict_access
authenticate_or_request_with_http_token do |token, options|
ApiKey.exists?(access_token: token)
end
end
end
My failing rspec test looks like:
it 'responds successfully to generic request because of key protection' do
api_key = ApiKey.create
api_key.save!
get :index
request.headers["token"] = api_key.access_token
expect(response).to be_success # test for the 200 status-code
end
with result: expected success? to return true, got false
I don't understand how I can inject the valid api_key in to the request so that the response will evaluate to true. Any ideas? Thanks.
Token Authentication expects a HTTP_AUTHORIZATION header in this format:
Token token="my-api-token"
Also, you'll want to set the header before the get :index line:
request.headers["HTTP_AUTHORIZATION"] = "Token token=\"#{api_key.access_token}\""
get :index
You can use the encode_credentials method instead if you prefer:
request.headers["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Token.encode_credentials(api_key.access_token)
Related
Using Rails 6, I'm redirecting from native ids (/users/id) to friendly_ids (/users/username) (following the answer brought here) which handles the redirect as such:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def redirect_resource_if_not_latest_friendly_id(resource)
if resource.friendly_id != params[:id]
redirect_to resource, status: 301
end
end
end
In my controller, I call the method as such:
# app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :set_user, only: [:show]
def show
redirect_resource_if_not_latest_friendly_id(set_user)
end
protected
def set_user
#user = User.friendly.find(params[:id])
end
end
It's working fine and I want to include the redirection in my test suite. I found answer and topics about how to do that with Rspec but I'm using Minitest and couldn't figure it out.
I tried a bunch of different ways (with params[:id], etc.) but to keep it simple let's say it's the following fixture, test and result.
Here is the fixture:
# test/fixtures/users.yml
one:
username: username
email: email#example.com
slug: username
Here is the test:
# test/controllers/users_controller_test.rb
class UsersControllerTest < ActionDispatch::IntegrationTest
test "should 301 redirect to the friendly_id" do
#user = users(:one)
get user_path(#user)
assert_redirected_to "/users/#{#user.slug}"
end
end
Here is the result of the test:
FAIL["test_should_301_redirect_to_the_friendly_id", #<Minitest::Reporters::Suite:0x00007fe716c66600 #name="UsersControllerTest">, 0.7785789999979897]
test_should_301_redirect_to_the_friendly_id#UsersControllerTest (0.78s)
Expected response to be a <3XX: redirect>, but was a <200: OK>
test/controllers/users_controller_test.rb:8:in `block in <class:UsersControllerTest>'
What am I doing wrong?
The problem is you're using the "whole" user record to make the request, so when you do
user_path(#user)
The route extracts the friendly_id from the resource and then your condition is evaluated to false, because the resource.friendly_id is always going to be the same as the id coming from the params.
Try instead:
get user_path(id: #user.id)
That way you can explicitly pass the #user.id through the params.
possible the problem resource.friendly_id != params[:id] as I understood they are the same
I'm having trouble trying to authenticate a request spec. How would I pass a valid auth token in the header of each http request? Is my approach below the correct?
tweets_request_spec.rb
require 'rails_helper'
RSpec.describe 'Tweets API', type: :request do
before do
#tweets = create_list(:tweet, 10)
#tweet = #tweets.first
end
describe 'GET /tweets' do
before { get '/tweets', { "Authorization": *some sort of token*} }
it "returns tweets" do
expect(json).to_not be_empty
expect(json).to eq(10)
end
it "is a successful http request" do
expect(response).to have_http_response(200)
end
end
end
Here is my code for the authentication controller, as well as the modules that help with generating and decoding the auth tokens that are passed in the http headers.
authentication_controller.rb
class AuthenticationController < ApplicationController
skip_before_action :authenticate_request
def authenticate
command = AuthenticateUser.call(params[:email], params[:password])
if command.success?
render json: { auth_token: command.result }
else
render json: { error: command.errors }, status: :authorized
end
end
end
authorize_api_request.rb
class AuthorizeApiRequest
prepend SimpleCommand
def initialize(headers = {})
#headers = headers
end
def call
user
end
private
attr_reader :headers
def user
#user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token
#user ||= errors.add(:token, 'Invalid token') && nil
end
#decode the auth token and retrieve the user id
def decoded_auth_token
#decoded_auth_token ||= JSONWebToken.decode(http_auth_header)
end
#retrieve auth token from header
def http_auth_header
if headers['Authorization'].present?
return headers['Authorization'].split(' ').last
else
errors.add(:token, 'Missing token')
end
end
end
Some Code Extracts copied from the the official pluralsight page
the endpoint to authenticate is in config/routes.rb
post 'authenticate', to: 'authentication#authenticate'
which executes this action. The action returns the token if you correctly authenticate.
def authenticate
command = AuthenticateUser.call(params[:email], params[:password])
if command.success?
render json: { auth_token: command.result }
else
render json: { error: command.errors }, status: :unauthorized
end
end
In rspec you have two options, you either mock this method or create a factory.
The concept of token based authentication is that once authenticated the user will have a token and by providing this token he will be able to access the functionalities only reserved to users
The request
$ curl -H "Content-Type: application/json" -X POST -d '{"email":"example#mail.com","password":"123123123"}' http://localhost:3000/authenticate
gives in response the token
{"auth_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE0NjA2NTgxODZ9.xsSwcPC22IR71OBv6bU_OGCSyfE89DvEzWfDU0iybMA"}
if you include in the header the token, the request will not trigger an authorization error
$ curl -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE0NjA2NTgxODZ9.xsSwcPC22IR71OBv6bU_OGCSyfE89DvEzWfDU0iybMA" http://localhost:3000/items []
so before doing your get request, include in the request header the token
request.headers['Authorization'] = auth_token
get :your_action
How to provide a correct value of auth_token?
You will need to mock the method authenticate_request in ApplicationController, as it is called before the action
#app/controllers/application_controller.rb
class ApplicationController < ActionController::API
before_action :authenticate_request
attr_reader :current_user
private
def authenticate_request
#current_user = AuthorizeApiRequest.call(request.headers).result
render json: { error: 'Not Authorized' }, status: 401 unless #current_user
end
end
I believe you should mock this line of code, to avoid receiving an authentication error.
#current_user = AuthorizeApiRequest.call(request.headers).result
so I would write the specs somethind like this
user = FactoryBot.create(:user)
allow(AuthorizeApiRequest).to receive(:call).and_return(user)
# request.headers['Authorization'] = auth_token # this is not required anymore the authentication is skipped
get :your_action
I quote pluralsight
By using before_action, the server passes the request headers (using the built-in object property request.headers) to AuthorizeApiRequest every time the user makes a request. Calling result on AuthorizeApiRequest.call(request.headers) is coming from SimpleCommand module where it is defined as attr_reader :result. The request results are returned to the #current_user, thus becoming available to all controllers inheriting from ApplicationController.
You can read more about mocking at
https://github.com/rspec/rspec-mocks
Hi thanks for viewing my question. I'm building out a Rails API which works with a React front end on a different server. I'm using the Devise Token Auth gem, and am able to successfully log in, log out, and make get requests for multiple resources without any problems. To handle the changing tokens I am updating the headers on each request.
The problem I am running into is when I try to make a PUT request to update a resource, in which case I get a 401. Full error message:
Started PUT "/api/stores/3/orders/1" for 127.0.0.1 at 2017-07-12 16:00:05 -0400
Processing by Api::OrdersController#update as HTML
Parameters: {"order"=>{"id"=>1, "provider_notes"=>"tailor notes"}, "headers"=>{"client"=>"YVa0NIlxAdm6BLQXk0xeJw", "access-token"=>"bNc9BB0TgICIJzGfM4H_6A", "uid"=>"joe#joestailor.com"}, "store_id"=>"3", "id"=>"1"}
Can't verify CSRF token authenticity.
Filter chain halted as :authenticate_user! rendered or redirected
Completed 401 Unauthorized in 1ms (Views: 0.1ms | ActiveRecord: 0.0ms)
After checking, the access-token printed in the error message is in fact the token i got from the last request, so it should be good. Here's the controller I am working with. I'm not able to get passed the authenticate_user! before action.
class Api::OrdersController < ApplicationController
before_action :authenticate_user!
before_action :set_order, only: [:show, :update]
def index
render :json => current_user.store.open_orders.as_json(include: [:customer], methods: [:alterations_count])
end
def show
render :json => #order.as_json(include: [:customer, :items => {include: :item_type}])
end
def update
if #order.update(order_params)
render :json => #order
.as_json(include: [:customer, :items => {include: :item_type}])
else
byebug
end
end
private
def set_order
#order = Order.find(params[:id])
end
def order_params
if current_user.tailor?
params.require(order).permit(:requester_notes, :arrived, :fulfilled)
end
end
end
Any reason why a put request might work differently than the get requests I've been using (for this same controller)? Any advice to get around this with the Devise Auth Token would be awesome. Thanks.
are you using :authenticate_user! from devise?
if yes, it can't work for api
you need create your own helper method
you can put it in your api application_controller/model_controller
or cretae a module and include it wherever you need
then change the before action into authenticate_with_token!
def current_user_api
#current_user ||= User.find_by(auth_token: request.headers['Authorization'])
end
def user_signed_in_api?
current_user_api.present?
end
def authenticate_with_token!
render json: { errors: "Not authenticated" },
status: :unathorized unless user_signed_in_api?
end
this book chapter 5 will help you
http://apionrails.icalialabs.com/book/chapter_five
and for Can't verify CSRF token authenticity.
you can put this in your application controller
protect_from_forgery with: :null_session
skip_before_action :verify_authenticity_token, if: :json_request?
protected
def json_request?
request.format.json?
end
If you want to use :authenticate_user! or similar, then make sure to pass in all the necessary parameters:
access-token
expiry
token-type
uid
client
It looks like you were missing expiry and token-type.
This will result in the user being authenticated and the current_user being set as expected, assuming the token and credentials are valid.
Note: these headers my need to be exposed in your CORS configuration:
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource "*",
expose: %w[access-token expiry token-type uid client],
headers: :any,
methods: :any
end
end
Another Note: If you mounted your devise endpoint inside the api, then you will need to call a different :authenticate_user! method.
example:
namespace :api, defaults: { format: 'json' } do
namespace :v1 do
mount_devise_token_auth_for 'User', at: 'auth'
# API routing and resources
end
end
Then you would need to call :authenticate_api_v1_user! instead.
example:
class Api::V1::ApiController < ApplicationController
before_action :authenticate_api_v1_user!
end
I want to allow access to a resource only when the authorization token contained in the HTTP headers matches token stored in the users table.
i'm using curl as shown below to access the resource:
$ curl http://localhost:3000/api/v1/tasks.json -H 'Authorization: Token token="S8S4MPqFNdDz3G1jLsC9"'
in the tasks#index method i would like to check whether the token above matches the current user's authentication token stored in the database.
How can i get the token value as an instance variable so that i can use it as shown below:
def index
#token = ???
if User.exists?(:authentication_token => #token)
### code to access the resource
else
authenticate_user!
end
end
From this railscast, I have found the best solution is to use rails authenticate_or_request_with_http_token method like this:
class Api::V1::TasksController < ApplicationController
before_filter :restrict_access
respond_to :json
def index
#user = User.where("users.authentication_token IS NOT NULL").first
#tasks = #user.tasks
end
private
def restrict_access
authenticate_or_request_with_http_token do |token, options|
User.exists?(authentication_token: token)
end
end
I am trying to stub authentication for a controller in rspec. When I stub the authorize method the test always passed no matter what the value I supply.
Controller:
class FoosController < ApplicationController
before_filter :authorize
...
end
ApplicationController:
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
protected
def authorize
return true if current_user
flash[:error] = 'Please login'
redirect_to signin_path
false
end
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
Specs:
# this passes (expected)
it "..." do
controller.stubs(:current_user).returns(User.new)
get :index
response.should be_success
end
# this fails (expected)
it "..." do
controller.stubs(:current_user).returns(nil)
get :index
response.should be_success
end
# this passes (expected)
it "..." do
controller.stubs(:authorize).returns(true)
get :index
response.should be_success
end
# Problem: this passes (unexpected)
it "..." do
controller.stubs(:authorize).returns(false)
get :index
response.should be_success
end
It seems like as soon as I stub :authorize, no matter what value is set, it always passes the before_filter. I thought it might be the protected/helper_method designations, but playing with those didn't change anything.
Why does stubbing :authorize with false cause the before_filter to pass?
I think you need to check WHAT is being rendered.
Looking at your code, if the call-chain does indeed stop when authorize returns false,
then what is going to happen?
There is no redirect, or render call.
So it will be an empty response?
An empty response would still be a 200.
However, depending what version of Rails you're using, its possible in Rails 3.1 that a before_filter returning false no longer stops the chain.
Really, a before_filter that wants to stop the chain should do one of the following
redirect somewhere
render something
raise something
I'll answer your last question Why does stubbing :authorize with false cause the before_filter to pass?
You're stubbing the method authorize, which literally stops all the code inside of it to be called but returns what you are explicitly returning with the stub.
It is working properly, because when you stub current_user to false then the authorize is called completely. I think your lasts tests are not actually testing anything for you but thats just my opinion.