rails / activerecord-session_store

Active Record's Session Store extracted from Rails
MIT License
540 stars 187 forks source link

Solution for encrypted session data and silenced logs #150

Open mediafinger opened 4 years ago

mediafinger commented 4 years ago

Encrypted database sessions

Hey, I would like to share my solution to how I use the gem activerecord-session_store to:

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.

breim commented 4 years ago

Buddy, You might improve this example, posting this entire class EncryptionService

mediafinger commented 4 years ago

@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.

breim commented 4 years ago

Hmmm interesting.

Thanks buddy ! :)