I’m using some HTTP basic authentication in Chirk HR as a simple way of preventing unauthorized access. It’s simple, fast, and easy to change to a more robust authentication later on.
Ideal Authentication Test
As part of my testing habits, I try to really exercise important methods. Authentication is definitely one of them. Authentication is also tricky because many of the authentication libraries give you helper methods that bypass some of the actual logging in process (e.g. open login page, enter username, enter password, submit). For this reason I try to test my authentication at the integration level:
- Load login form
- Enter authentication data and submit
- Verify login was successful
My integration tool of choice is capybara. Its DSL is high level enough for me to model how a visitor behaves without being too complex.
Testing HTTP Basic Authentication
The problem with supporting different drivers is that the capybara can only abstract so much, as each browser supports different features such as HTTP basic. Fortunately, with Ruby it is easy to inspect each driver’s methods and call its version of HTTP basic as needed. By abstracting these into a method, our tests now have a clean way to use HTTP basic with capybara.
require 'test_helper' class Admin::CohortTest < ActionDispatch::IntegrationTest def basic_auth(name, password) if page.driver.respond_to?(:basic_auth) page.driver.basic_auth(name, password) elsif page.driver.respond_to?(:basic_authorize) page.driver.basic_authorize(name, password) elsif page.driver.respond_to?(:browser) && page.driver.browser.respond_to?(:basic_authorize) page.driver.browser.basic_authorize(name, password) else raise "I don't know how to log in!" end end test "should block access without invalid HTTP auth" do visit '/admin' assert_equal 401, page.status_code end test "should show the page" do basic_auth('edavis', 'password') visit '/admin' assert_equal 200, page.status_code assert has_content?("Cohorts") end end
Looking back at the ideal authentication test we can see that this is very close to fulfilling the behavior we want.
basic_authmethod enters the authentication data and submits.
visittries to open a HTTP basic protected page, which opens the login form.
- The final
assert has_content?("Cohorts")verifies that the login was successful.
Because of how HTTP basic works the flow is a bit different in that we are setting the authentication first and then loading the page. It’s not ideal but for something quick I think it’s understandable enough.
Ideally, you’d extract the
basic_auth method to the test helper so you can reuse it in other tests.
There are a few gotchas to be aware of when using and testing HTTP basic this way:
- With HTTP Basic, authentication is passed in the clear to the server. Depending on your application this might be a problem. On Chirk HR I’m using SSL for everything so the HTTP Basic headers are encrypted there. For something stronger than HTTP Basic, try using HTTP Digest or a full authentication system.
- The test is calling methods on the underlying driver which could break if you switch capybara drivers. You’ll want to standardize on one or two drivers for you application to keep things simple.
- Also if the drivers change their HTTP basic APIs in the future then your tests might start failing.
Using and testing HTTP Basic authentication in Rails doesn’t need to be difficult. While it isn’t as fully featured as full authentication systems like devise or sorcery, HTTP Basic could be a good enough system for you to use in your application.
Thanks to the Stackoverflow post where I learned this.