I am trying to get devise and devise-jwt gems to work so I can implement Authorization into my API only Rails app.
I have installed both devise and devise-jwt gems.
I followed the instructions on this blog post:
https://medium.com/#mazik.wyry/rails-5-api-jwt-setup-in-minutes-using-devise-71670fd4ed03
I have implemented the request specs that the author has included in his post and I can't get it approved on "Deleted",
I have to pass the authorizate token on delete, but I'm not getting it.
Any suggestion?
require 'rails_helper'
require "json"
RSpec.describe "POST /users", type: :request do
let(:user) { create(:user) }
let(:url) { '/users/sign_in' }
let(:params) do
{
user: {
email: user.email,
password: user.password
}
}
end
context 'when params are correct' do
before do
post url, params: params.to_json, headers: { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
end
it 'returns 200' do
expect(response).to have_http_status(200)
end
it 'returns JTW token in authorization header' do
expect(response.headers['authorization']).to be_present
end
end
context 'when login params are incorrect' do
before { post url }
it 'returns unathorized status' do
expect(response.status).to eq 401
end
end
end
RSpec.describe 'DELETE /logout', type: :request do
let(:url) { '/users/sign_out' }
it 'returns 204, no content' do
delete url
expect(response).to have_http_status(201)
end
end
I need to pass the user's token on delete, any suggestions on how I can be doing this?
I have a basic Rails API built with Accounts and Users. All of the account specs pass when I remove...
before_action :authenticate_user!
But with that in place, I'm having trouble getting the specs to pass.
# Note `json` is a custom helper to parse JSON responses
RSpec.describe 'Account API', type: :request do
# test data
let!(:user) { create(:user) }
let!(:accounts) { create_list(:account, 10, user_id: user.id) }
let(:account_id) { accounts.first.id }
# GET /accounts
describe 'GET /accounts' do
# HTTP request before examples
before do
get '/accounts'
request.headers.merge! user.create_new_auth_token
end
it 'returns accounts' do
expect(json).not_to be_empty
expect(json.size).to eq(10)
end
it 'returns status code 200' do
expect(response).to have_http_status(200)
end
end
end
As you can see I attempted to merge the auth token with this line in a before do block...
request.headers.merge! user.create_new_auth_token
But that is not working. Instead I get json.size == 1 and http_status of 401 unauthorized.
I was able to fix it by declaring a variable at the top...
let(:auth_headers) { create(:user).create_new_auth_token }
And in my specs using it like this...
before { get '/accounts', headers: auth_headers }
I have a spec type: :request and I want to add authentication via ApiAuth. How can i do that?
ApiAuth use request object for authentication
ApiAuth.sign!(request, user.id, user.api_secret_key)
And I can use it in specs for controllers as follows
request.env['HTTP_ACCEPT'] = 'application/json'
user = defined?(current_user) ? current_user : Factory.create(:admin_user)
ApiAuth.sign!(request, user.id, user.api_secret_key)
But request object is missing in specs for requests
describe 'Cities API', type: :request do
let(:cities) { City.all }
let(:admin_user) { Factory.create(:admin_user) }
context 'given an unauthorized request' do
it 'returns 401 status' do
get '/api/cities'
expect(response).to have_http_status(:unauthorized)
end
end
context 'given an authorized request' do
it 'sends a list of cities' do
# i need authorization here
get '/api/cities'
expect(response).to be_success
end
end
end
Now I stub authentication
describe 'Cities API', type: :request do
let(:cities) { City.all }
let(:admin_user) { Factory.create(:admin_user) }
before { Factory.create(:route) }
context 'given an unauthorized request' do
it 'returns 401 status' do
get '/api/cities'
expect(response).to have_http_status(:unauthorized)
end
end
context 'given an authorized request' do
before(:each) do
allow_any_instance_of(CitiesController).to receive(:authenticate_user!).and_return(true)
allow_any_instance_of(CitiesController).to receive(:admin_user).and_return(admin_user)
end
it 'sends a list of cities' do
get '/api/cities'
expect(response).to be_success
end
end
end
But I want to avoid of using stubs in request specs.
Does anybody have any ideas?
I created issue on github. You can watch my solution here
I am using Devise and JWT's to authenticate users in a project I am writing. I am having a hard time figuring out how to write a useful test to expect a JWT response.body (since each is encrypted).
The only thing I can think of is to test that they are structured as a JWT should be (a 3 segment, '.' delimited string).
Has anyone encountered testing random/hashed returns and come up with a better solution?
describe SessionTokensController, type: :controller do
let(:current_user) { FactoryGirl.create(:user) }
before(:each) do
sign_in current_user
end
describe '#create' do
it 'responds with a JWT' do
post :create
token = JSON.parse(response.body)['token']
expect(token).to be_kind_of(String)
segments = token.split('.')
expect(segments.size).to eql(3)
end
end
end
It really depends on what exactly you want to test.
If you simply want to test if the returned token exists and is valid you can do the following:
it 'responds with a valid JWT' do
post :create
token = JSON.parse(response.body)['token']
expect { JWT.decode(token, key) }.to_not raise_error(JWT::DecodeError)
end
Although it seems much more useful to validate the claims that the token includes:
let(:claims) { JWT.decode(JSON.parse(response.body)['token'], key) }
it 'returns a JWT with valid claims' do
post :create
expect(claims['user_id']).to eq(123)
end
In the latter example you can validate the exact claims you included in the JWT.
let(:user) { create(:user, password: "123456") }
describe "POST authenticate_user" do
context "with a valid password" do
it "authenticates successfully" do
post :authenticate_user, params:{email: user.email, password: "123456"}, format: :json
parsed_body = JSON.parse(response.body)
# binding.pry
expect(parsed_body.keys).to match_array(["auth_token", "user"])
expect(parsed_body['user']['email']).to eql("joe#gmail.com")
expect(parsed_body['user']['id']).to eql(user.id)
end
it "authentication fails" do
post :authenticate_user, params:{email: user.email, password: "123456789"}, format: :json
parsed_body = JSON.parse(response.body)
expect(parsed_body['errors'][0]).to eql("Invalid Username/Password")
end
end
end
Here my http basic authentication in the application controller file (application_controller.rb)
before_filter :authenticate
protected
def authenticate
authenticate_or_request_with_http_basic do |username, password|
username == "username" && password == "password"
end
end
and the default test for the index action of my home controller (spec/controllers/home_controller_spec.rb)
require 'spec_helper'
describe HomeController do
describe "GET 'index'" do
it "should be successful" do
get 'index'
response.should be_success
end
end
Test doesn't run because of the authentication method. I could comment "before_filter :authenticate" to run them but I would like to know if there is way to make them worked with the method.
Thank you!
Update (2013): Matt Connolly has provided a GIST which also works for request and controller specs: http://gist.github.com/4158961
Another way of doing this if you have many tests to run and don't want to include it everytime (DRYer code):
Create a /spec/support/auth_helper.rb file:
module AuthHelper
def http_login
user = 'username'
pw = 'password'
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user,pw)
end
end
In your test spec file:
describe HomeController do
render_views
# login to http basic auth
include AuthHelper
before(:each) do
http_login
end
describe "GET 'index'" do
it "should be successful" do
get 'index'
response.should be_success
end
end
end
Credit here - Archived site
Sorry I didn't seek enough, the solution seems to be the following:
describe "GET 'index'" do
it "should be successful" do
#request.env["HTTP_AUTHORIZATION"] = "Basic " + Base64::encode64("username:password")
get 'index'
response.should be_success
end
end
Some answers suggest to set request.env which is unsafe, because request can be nil and you will end up with private method env' called for nil:NilClass, especially when run single tests with rspec -e
Correct approach will be:
def http_login
user = 'user'
password = 'passw'
{
HTTP_AUTHORIZATION: ActionController::HttpAuthentication::Basic.encode_credentials(user,password)
}
end
get 'index', nil, http_login
post 'index', {data: 'post-data'}, http_login
For me, with Rails 6, I need keyword arguments for rspec get method like .. get route, params: params, headers: headers
Auth Helper method
module AuthHelper
def headers(options = {})
user = ENV['BASIC_AUTH_USER']
pw = ENV['BASIC_AUTH_PASSWORD']
{ HTTP_AUTHORIZATION: ActionController::HttpAuthentication::Basic.encode_credentials(user,pw) }
end
def auth_get(route, params = {})
get route, params: params, headers: headers
end
end
and the rspec request test.
describe HomeController, type: :request do
include AuthHelper
describe "GET 'index'" do
it "should be successful" do
auth_get 'index'
expect(response).to be_successful
end
end
end
When using Rspec to test Grape APIs, the following syntax works
post :create, {:entry => valid_attributes}, valid_session
where valid_session is
{'HTTP_AUTHORIZATION' => credentials}
and
credentials = ActionController::HttpAuthentication::Token.encode_credentials("test_access1")
These are great solutions for controller and request specs.
For feature tests using Capybara, here is a solution to make HTTP Basic authentication work:
spec/support/when_authenticated.rb
RSpec.shared_context 'When authenticated' do
background do
authenticate
end
def authenticate
if page.driver.browser.respond_to?(:authorize)
# When headless
page.driver.browser.authorize(username, password)
else
# When javascript test
visit "http://#{username}:#{password}##{host}:#{port}/"
end
end
def username
# Your value here. Replace with string or config location
Rails.application.secrets.http_auth_username
end
def password
# Your value here. Replace with string or config location
Rails.application.secrets.http_auth_password
end
def host
Capybara.current_session.server.host
end
def port
Capybara.current_session.server.port
end
end
Then, in your spec:
feature 'User does something' do
include_context 'When authenticated'
# test examples
end
My solution:
stub_request(method, url).with(
headers: { 'Authorization' => /Basic */ }
).to_return(
status: status, body: 'stubbed response', headers: {}
)
Use gem webmock
you can tighten verification by change:
/Basic */ -> "Basic #{Base64.strict_encode64([user,pass].join(':')).chomp}"
URL - can be a regular expression