activerecord-session_store
activerecord-session_store copied to clipboard
Solution for encrypted session data and silenced logs
Encrypted database sessions
Hey, I would like to share my solution to how I use the gem activerecord-session_store to:
- store sessions in the database
- encrypt and decrypt the session data on the fly
- silence the logs
When you need a very fast solution, you might want to stick to encrypted cookies in the user's browser. The particular use case for this solution is a small administration app that will never have thousands of users, but is used in a security and privacy aware context.
config/initializers/session_store.rb
ActiveRecord::SessionStore::Session.table_name = "sessions"
ActiveRecord::SessionStore::Session.primary_key = "session_id"
ActiveRecord::SessionStore::Session.data_column_name = "data"
ActiveRecord::SessionStore::Session.serializer = :json
ActionDispatch::Session::ActiveRecordStore.session_class = ::Session
Rails.application.config.session_store :active_record_store, key: "_encrypted_session"
app/models/session.rb
class Session < ApplicationRecord
self.primary_key = :session_id
around_save :silence_logs
class << self
def find_by_session_id(session_id)
Session.find_or_initialize_by(session_id: session_id)
end
end
def session_id=(sid)
@session_id = sid || SecureRandom.hex(16)
super(@session_id)
end
def session_id
read_attribute(:session_id) || @session_id
end
def data=(json)
super(EncryptionService.new(salt: "your salt").encrypt(json))
end
def data
encrypted_data = read_attribute(:data)
EncryptionService.new(salt: "your salt").decrypt(encrypted_data) unless session_id.nil? || encrypted_data.blank?
# rescue in case the secret changed (no rollover implemented yet)
# the salt is wrong
# or some other issue prevented decryption
# and delete the flawed session data
rescue ActiveSupport::MessageEncryptor::InvalidMessage
delete && nil
end
private
# simple, reliable log silencing
def silence_logs
Rails.logger.silence do
yield # saves / updates the session
end
end
end
The EncryptionService
used in this example is a small class based on ActiveSupport::MessageEncryptor
database schema
The index on the updated_at
column is there to delete sessions older than 30 days by running rake db:sessions:trim
as a scheduled task.
create_table "sessions", primary_key: "session_id", id: :string, force: :cascade do |t|
t.text "data"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["session_id"], name: "index_sessions_on_session_id", unique: true
t.index ["updated_at"], name: "index_sessions_on_updated_at"
end
Hope it helps!
By going through the issues here and while trying to implement this solution, I've got the impression that the documentation of this gem is outdated and lacking. Maybe this implementation helps someone to achieve something similar faster than me.
Buddy, You might improve this example, posting this entire class EncryptionService
@breim My implementation of the EncryptionService turned out to be a bit too CPU intense to use it with many concurrent sessions. So, I prefer to not add it, so it won't be blindly copied.
But if you look for examples using ActiveSupport::MessageEncryptor
you will get the idea.
Hmmm interesting.
Thanks buddy ! :)