Skip to content

Instantly share code, notes, and snippets.

@KieranP
Last active October 8, 2020 16:11
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save KieranP/9044432 to your computer and use it in GitHub Desktop.
Save KieranP/9044432 to your computer and use it in GitHub Desktop.
A Ruby on Rails implementation of a Database and Cookie hybrid session storage for Devise that supports session revocation and storage of session ip and user agent for security (similar to Github)
# A user session class, a simplified mix of the following code samples:
# * https://github.com/blog/1661-modeling-your-app-s-user-session
# * http://www.jonathanleighton.com/articles/2013/revocable-sessions-with-devise/
class Session < ActiveRecord::Base
# Uncomment if you use Hobo Fields, else add these yourself
# fields do
# session_id :string, :index => true, :unique => true
# accessed_at :datetime
# user_ip :string
# user_agent :string
# timestamps
# end
belongs_to :user
validates_presence_of :user, :session_id
def self.exists?(user, session_id)
self.where(:user => user, :session_id => session_id).exists?
end
def self.fetch(user, session_id)
if user
user.sessions.find_by_session_id(session_id)
else
Session.find_by_session_id(session_id)
end
end
def self.activate(user)
self.purge_old(user) # Clear old sessions before creating one
user.sessions.create!(:session_id => SecureRandom.hex)
end
def self.deactivate(user, session_id)
self.fetch(user, session_id).try(:delete)
end
def self.purge_old(user)
user.sessions.where { accessed_at < 2.weeks.ago }.destroy_all
end
def accessed!(request)
self.accessed_at = Time.zone.now
self.user_ip = request.remote_ip
self.user_agent = request.user_agent
self.save!
end
end
class User < ActiveRecord::Base
has_many :sessions, :dependent => :destroy
end
Warden::Manager.after_set_user(:except => :fetch) do |user, warden, opts|
unless Rails.env.test?
Session.deactivate(user, warden.cookies.signed["_session_id"])
warden.cookies.signed["_session_id"] = {
:value => Session.activate(user).session_id,
:expires => 1.year.from_now,
:httponly => true
}
end
end
Warden::Manager.after_fetch do |user, warden, opts|
unless Rails.env.test?
if Session.exists?(user, warden.cookies.signed["_session_id"])
warden.cookies.signed["_session_id"] = {
:value => warden.cookies.signed["_session_id"],
:expires => 1.year.from_now,
:httponly => true
}
else
warden.logout
throw(:warden, :message => :unauthenticated)
end
end
end
Warden::Manager.before_logout do |user, warden, opts|
unless Rails.env.test?
Session.deactivate(user, warden.cookies.signed["_session_id"])
warden.cookies.delete("_session_id")
end
end
class ApplicationController < ActionController::Base
before_filter :authenticate_user!, :update_current_session
def current_session
return unless current_user
@current_session ||= current_user.sessions.find_by_session_id(cookies.signed[:_session_id])
end
def update_current_session
return unless current_session
current_session.accessed!(request)
end
end
# We use a hybrid database/cookie based session storage
[APP_CONSTANT]::Application.config.session_store :cookie_store, key: APP_CONFIG[:session_key]
@chrhansen
Copy link

Just arrived here from http://www.jonathanleighton.com/articles/2013/revocable-sessions-with-devise/
I wonder why you removed the exclusive option he had? I understand it was to remove all other active sessions for a user.
Thanks

@MichaelSid
Copy link

Hi Christian,

Do you need to remove all active sessions for a given user, upon logout?

Not sure if this is still relevant to you but I found a more effective way, by modifying the before_logout Warden callback:

Warden::Manager.before_logout do |user, warden, opts|
unless Rails.env.test?
user.sessions.delete_all
warden.cookies.delete("_session_id")
end
end

The exclusive option in the article was deleting all the sessions for ALL the users. Not quite sure why.

@abcnever
Copy link

did you figure out how to add the Warden::Manage callbacks without checking for test environment? I'd like to write rspec test for my controller that involves revoking user's other login sessions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment