multi_session
multi_session
is a Railtie that extends rails to provide the ability to have multiple sessions per user via encrypted cookies.
Motivation
Rails comes with a session
helper method for storing user session information across HTTP requests. The default approach is to store that information in an encrypted cookie that gets passed as a header in each HTTP request/response. That cookie is encrypted/decrypted using the secret_key_base
defined in config/secrets.yml
(or config/credentials.yml.enc
as of Rails version 5.2).
Rails' session
is secure and works great but what if you'd like to share that session cookie with multiple Rails applications hosted across different subdomains? Various blogs and stackoverflow questions on the matter suggest sharing the same secret_key_base
value amongst the multiple Rails applications. But what if these apps only want to share part of the session information? Or what if there are security concerns because secret_key_base
is used for more than just encrypting session cookies? This multi_session
railtie provides a helper method (named multi_session
) similar to session
except that it permits you to create multiple encrypted session cookies per user, each with their own secret key, giving you the flexibility to choose which session components could be shared with other Rails (and non-Rails) web applications.
Usage
multi_session
is a helper method that gets added to your ApplicationController
and is therefore accessible by all your controllers that subclass ApplicationController
, as well all view templates. To create and read new sessions, use bracket notation ([]
and []=
) like you would with Hash
or hash-like objects.
For example:
# app/controllers/some_controller.rb
class SomeController < ApplicationController
def my_action
@user = User.find(params.require(:id))
multi_session[:global_user_session] = {
'user_id' => @user.id,
'email' => @user.email,
'name' => @user.full_name
}
multi_session[:user_preferences] = {
'enable_push_notifications' => true,
'something_else' => false
}
end
def another_action
@user = User.find(multi_session[:global_user_session]['user_id'])
end
end
In the example above, multi_session would create 2 encrypted cookies, one named "global_user_session"
and the other named "user_preferences"
. The key_base used to encrypt/decrypt these cookies would need to be defined in config/secrets.yml
or config/credentials.yml.enc
under a key named :multi_session_keys
. Example:
# config/credentials.yml.enc
secret_key_base: # generated by Rails
multi_session_keys: # use `rake secret` to generate custom keys
global_user_session: # insert a new secret here
user_preferences: # insert a different secret here
Installation and Requirements
Add this line to your application's Gemfile:
gem 'multi_session', '~> 1.1'
Currently multi_session
will only work with Rails version 5.2.0 or higher. In version 5.2, Rails switched the default session encryption from aes-256-cbc
to aes-256-gcm
. This gem has only been coded to work with the aes-256-gcm
cipher which unfortunately does not work with older versions of ActiveSupport::MessageEncryptor
.
Configuration
For the current version multi_session
, there these are the configuration values that can optionally be set:
Config option | Type | Description |
---|---|---|
expires |
ActiveSupport::Duration | expiration period for multi_session cookies/values |
domain |
String/Symbol/Array | domain for the multi_session cookies |
authenticated_encrypted_cookie_salt |
String | Salt used to derive key for GCM encryption |
credentials_strategy |
String/Symbol | Strategy for managing credentials. |
To configure multi_session
, first generate an initializer using the built-in rails generator:
rails g multi_session:install
Then open and edit config/initializers/multi_session.rb
:
# config/initializers/multi_session.rb
MultiSession.setup do |config|
# Uncomment to force multi_session cookies to expire after a period of time
config.expires = 30.minutes
# Uncomment to change the domain of the multi_session cookies
# config.domain = nil
# Salt used to derive key for GCM encryption. Default value is 'multi session authenticated encrypted cookie'
config.authenticated_encrypted_cookie_salt = 'my multi session salt value'
# Specify the strategy by which you are managing credentials in your application
# Defaults to :credentials or :secrets depending on what your application is using
# 'credentials' (default) - uses Rails encrypted credentials stored in credentials.yml.enc via Rails.application.credentials
# 'secrets' - uses Rails secrets specified in secrets.yml via Rails.application.secrets
# 'creds' - uses the [Creds](https://github.com/freeletics/creds) gem via Rails.configuration.creds
config.credentials_strategy = 'credentials'
end
Security
multi_session
does not introduce any novel security mechanisms. Encryptions/decryption is done using ActiveSupport::MessageEncryptor
in the same manner that Rails encrypts the session
cookie in Rails 5.2 (see https://github.com/rails/rails/blob/5fb4703471ffb11dab9aa3855daeef9f592f6388/actionpack/lib/action_dispatch/middleware/cookies.rb).
The default cipher for encrypting messages in Rails 5.2 is AES-256-GCM
, which is the same as what multi_session
uses.
There are many good writeups on the web (such as https://guides.rubyonrails.org/security.html, https://www.justinweiss.com/articles/how-rails-sessions-work/, and https://www.theodinproject.com/courses/ruby-on-rails/lessons/sessions-cookies-and-authentication) that go into detail of how Rails addresses session security concerns and I encourage all app developers to spend some time educating themselves on how Rails sessions work.
The implementation of multi_session
is actually quite simple because it leans heavily on existing Rails functionality. The nuts and bolts are primarily coded in lib/multi_session/session.rb
(https://github.com/seanhuber/multi_session/blob/b0211d714d995dc01eb817e52f5dc78e52120bf0/lib/multi_session/session.rb). It's a small file, please do your own code-audit before using this gem. 😉
Contributing
Pull requests and issue reports are welcome!
License
The gem is available as open source under the terms of the MIT License.