Skip to content

Solution for encrypted session data and silenced logs #150

Open
@mediafinger

Description

@mediafinger

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions