Back to Repositories

Testing HTTP Authentication Implementation in Devise

This integration test suite validates HTTP authentication functionality in Devise, focusing on basic authentication, session storage, and custom authentication configurations. The tests ensure proper handling of authentication headers, response formats, and various authentication scenarios.

Test Coverage Overview

The test suite provides comprehensive coverage of HTTP authentication mechanisms in Devise:

  • Basic HTTP authentication validation
  • Session storage behavior with different configurations
  • Custom realm support
  • Authentication key handling
  • OAuth2 header differentiation

Implementation Analysis

The testing approach utilizes Devise’s IntegrationTest framework to verify HTTP authentication flows. It employs configuration swapping to test different scenarios and validates both successful and failed authentication attempts. The implementation leverages Minitest assertions and Devise’s test helpers for thorough verification.

Technical Details

  • Testing Framework: Minitest
  • Authentication Methods: Basic HTTP, OAuth2
  • Response Formats: JSON
  • Key Components: Warden authentication, HTTP headers, Custom realms
  • Configuration: Devise swap mechanism for temporary config changes

Best Practices Demonstrated

The test suite exemplifies several testing best practices including isolation of test scenarios, comprehensive edge case coverage, and proper setup/teardown patterns. It demonstrates effective use of helper methods, clear test naming, and thorough validation of both success and failure scenarios.

heartcombo/devise

test/integration/http_authenticatable_test.rb

            
# frozen_string_literal: true

require 'test_helper'

class HttpAuthenticationTest < Devise::IntegrationTest
  test 'sign in with HTTP should not run model validations' do
    sign_in_as_new_user_with_http

    assert_not User.validations_performed
  end

  test 'handles unverified requests gets rid of caches but continues signed in' do
    swap ApplicationController, allow_forgery_protection: true do
      create_user
      post exhibit_user_url(1), headers: { "HTTP_AUTHORIZATION" => "Basic #{Base64.encode64("[email protected]:12345678")}" }
      assert warden.authenticated?(:user)
      assert_equal "User is authenticated", response.body
    end
  end

  test 'sign in should authenticate with http' do
    swap Devise, skip_session_storage: [] do
      sign_in_as_new_user_with_http
      assert_response 200
      assert_match '"email":"[email protected]"', response.body
      assert warden.authenticated?(:user)

      get users_path(format: :json)
      assert_response 200
    end
  end

  test 'sign in should authenticate with http but not emit a cookie if skipping session storage' do
    swap Devise, skip_session_storage: [:http_auth] do
      sign_in_as_new_user_with_http
      assert_response 200
      assert_match '"email":"[email protected]"', response.body
      assert warden.authenticated?(:user)

      get users_path(format: :json)
      assert_response 401
    end
  end

  test 'returns a custom response with www-authenticate header on failures' do
    sign_in_as_new_user_with_http("unknown")
    assert_equal 401, status
    assert_equal 'Basic realm="Application"', headers["WWW-Authenticate"]
  end

  test 'uses the request format as response content type' do
    sign_in_as_new_user_with_http("unknown")
    assert_equal 401, status
    assert_equal "application/json; charset=utf-8", headers["Content-Type"]
    assert_match '"error":"Invalid Email or password."', response.body
  end

  test 'returns a custom response with www-authenticate and chosen realm' do
    swap Devise, http_authentication_realm: "MyApp" do
      sign_in_as_new_user_with_http("unknown")
      assert_equal 401, status
      assert_equal 'Basic realm="MyApp"', headers["WWW-Authenticate"]
    end
  end

  test 'sign in should authenticate with http even with specific authentication keys' do
    swap Devise, authentication_keys: [:username] do
      sign_in_as_new_user_with_http("usertest")
      assert_response :success
      assert_match '"email":"[email protected]"', response.body
      assert warden.authenticated?(:user)
    end
  end

  test 'it uses appropriate authentication_keys when configured with hash' do
    swap Devise, authentication_keys: { username: false, email: false } do
      sign_in_as_new_user_with_http("usertest")
      assert_response :success
      assert_match '"email":"[email protected]"', response.body
      assert warden.authenticated?(:user)
    end
  end

  test 'it uses the appropriate key when configured explicitly' do
    swap Devise, authentication_keys: { email: false, username: false }, http_authentication_key: :username do
      sign_in_as_new_user_with_http("usertest")
      assert_response :success
      assert_match '"email":"[email protected]"', response.body
      assert warden.authenticated?(:user)
    end
  end

  test 'test request with oauth2 header doesnt get mistaken for basic authentication' do
    swap Devise, http_authenticatable: true do
      add_oauth2_header
      assert_equal 401, status
      assert_equal 'Basic realm="Application"', headers["WWW-Authenticate"]
    end
  end

  private
    def sign_in_as_new_user_with_http(username = "[email protected]", password = "12345678")
      user = create_user
      get users_path(format: :json), headers: { "HTTP_AUTHORIZATION" => "Basic #{Base64.encode64("#{username}:#{password}")}" }
      user
    end

    # Sign in with oauth2 token. This is just to test that it isn't misinterpreted as basic authentication
    def add_oauth2_header
      user = create_user
      get users_path(format: :json), headers: { "HTTP_AUTHORIZATION" => "OAuth #{Base64.encode64("#{user.email}:12345678")}" }
    end
end