Ruby on Rails 8 introduces authentication scaffolding, so that you can create authentication in your app by just running: rails g authentication.
This works great in general. But this new scaffolding isn’t well documented. This post is meant to better document, and to clarify, one side-effect of this new scaffolding.
When you run rails g authentication, Rails creates several files, but the most interesting one is the authentication concern file: app/controllers/concerns/authentication.rb. This concerns file includes the key helper methods to string together an authentication system between your views, controllers, and models. For example, the authentication.rb file contains methods to check if a user is authenticated, resuming sessions, ending sessions, etc.
However, I ran into a lengthy debugging session due to the way in which authentication.rb expects signed cookies, and how this isn’t supported by default when using MiniTest in Rails’ test environment. Here’s why: in that authentication.rb concerns file, we have this method:
def find_session_by_cookie Session.find_by(id: cookies.signed[:session_id])endWhat’s a signed cookie you might ask? Cookies are simply objects that retain information about who you are as a user. There’s an RFC for cookies, so they’re a core component of how the modern web works. And modern browsers support using cookies to retain information about you the web user - typically session information so you don’t have to login between page refreshes.
So, browsers and Rails support cookies and use them to keep state of the user’s session information. And thanks to the .signed method, this cookie, this hash of user data, should be read-only. Otherwise, a malicious user would be able to modify their cookie’s state.
But when Rails is in its test environment, we have to build this cookie, otherwise you’ll get an error (as of Rails 8.0.0.1 at least). And currently, Rails doesn’t document this issue in its public guides.
So what do we do?
In my test_helper.rb file, I created an AuthenticationHelpers module:
module AuthenticationHelpers def sign_in_as(user) Current.session = user.sessions.create!
ActionDispatch::TestRequest.create.cookie_jar.tap do |cookie_jar| cookie_jar.signed[:session_id] = Current.session.id cookies[:session_id] = cookie_jar[:session_id] end endend
# Include the helpers in controller testsclass ActionDispatch::IntegrationTest include AuthenticationHelpersendThis ActionDispatch::TestRequest is key: it’s allowing us to create a signed cookie that plays well with our test environment. So the above code, basically:
- Creates a
Currentuser session - Builds a “fake” signed cookie for that user
Now, when I need to simulate login behavior I simply include the sign_in_as method. The below essentially logs the user in and creates a signed-in user. This allows us to pass the default expectations in authentication.rb
setup do @plan = plans(:one) @user = users(:one)
sign_in_as(@user) endNote that my above example demonstrated setting up this sign_in_as functionality for controller tests, but you can just as easily include this for other test types, like system tests:
class ActionDispatch::IntegrationTest include AuthenticationHelpersendI hope the above was helpful, and filled in some gaps of Rails 8’s authentication scaffolding.