76

Here's 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

The test doesn't run because of the authentication method. I could comment out "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!

Dogweather
  • 15,512
  • 17
  • 62
  • 81
benoitr
  • 6,025
  • 7
  • 42
  • 67

7 Answers7

145

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

notapatch
  • 6,569
  • 6
  • 41
  • 45
iwasrobbed
  • 46,496
  • 21
  • 150
  • 195
  • 1
    it shows request is nil for me. any idea how to get a workaround? – chourobin Apr 05 '12 at 20:01
  • 6
    For request specs, you can have multiple requests, so `request` is `nil`. Instead you need to create an env hash `env = {}`, update that in your http_login method, and then pass in the env explicitly as in `get '/', {}, env`. – Jonathan Tran Oct 24 '12 at 04:21
  • 6
    Expanding on the above, which works for request and controller specs: https://gist.github.com/4158961 – Matt Connolly Nov 28 '12 at 04:05
  • +1 for the gist from Matt Connolly; it solved my problem when nothing else did. BTW, I tried to use the Rack::Test authorize method - didn't work. – JESii Mar 02 '13 at 12:01
  • 1
    I get Failure/Error: @request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Token.encode_credentials("test_access1") NoMethodError: undefined method `env' for nil:NilClass when I try to use this.. why ? – deepwinter Jun 19 '14 at 05:59
  • A slightly more convenient take on this: https://gist.github.com/lehresman/794f261708c82962763f – Luke Ehresman Jul 29 '15 at 13:48
  • It's worth mentioning, that you can also pass auth info directly as a 3rd argument: `get '/repp/v1/contacts', { limit: 1, details: true }, { 'HTTP_AUTHORIZATION' => encoded_auth_key }` – Artur INTECH Dec 01 '16 at 13:47
20

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
benoitr
  • 6,025
  • 7
  • 42
  • 67
8

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
Daniel Garmoshka
  • 5,849
  • 39
  • 40
7

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
Ken Ratanachai S.
  • 3,307
  • 34
  • 43
4

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")
deepwinter
  • 4,568
  • 2
  • 31
  • 37
3

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
madcow
  • 2,567
  • 14
  • 31
  • Very helpful ! :) –  May 16 '17 at 20:49
  • Doesn't work, in Chrome, IE and Safari (at least) [you can't log in http auth with a url anymore](https://medium.com/@lmakarov/say-goodbye-to-urls-with-embedded-credentials-b051f6c7b6a3) – scoudert Jul 17 '19 at 12:54
0

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

Viktor Ivliiev
  • 1,015
  • 4
  • 14
  • 21