Modeling your App’s User Session
If you’ve been keeping an eye on your cookies, you may have noticed some recent changes GitHub has made to how we track your session. You shouldn’t notice any difference…
If you’ve been keeping an eye on your cookies, you may have noticed some recent changes GitHub has made to how we track your session. You shouldn’t notice any difference in session behavior (beyond the new ability to revoke sessions), but we’d like to explain what prompted the change.
Replay attacks on stateless session stores have been known and documented for quite some time in Rails and Django. Using signed cookies for sessions is still incredibly easy to use and scales to high traffic web apps. You just need to understand its limitations. When implementing authentication, simply storing a user ID in the session cookie leaves you open to replay attacks and provides no means for revocation.
The other option is to switch to persisted storage for sessions. Either using a database, memcache or redis. On a high traffic site this may be a performance concern since a session may be allocated even for anonymous browsing traffic. Another downside, there is no clear insight into these sessions. They are stored as serialized objects. So there’s no way to query the store to see if a user has any sessions. It’s all abstracted away by Rails.
Hybrid Cookie Store / DB approach
After ruling out Rails’ built in method for DB backed sessions, we decided that the concept of user sessions ought to be treated as a first class domain concern. Something with a real application API we can query, test and extend with other app concerns.
The UserSession
class is just a normal ActiveRecord class like any other. There’s no excess Rails abstraction layer between it. We’ve extended it with other concerns such as manual revocation, sudo mode tracking and data like IP and user agent to help users identify sessions on the active sessions page.
class UserSession < ActiveRecord::Base
belongs_to :user
before_validation :set_unique_key
scope :active, lambda {
{ :conditions => ["accessed_at >= ? AND revoked_at == NULL", 2.weeks.ago] }
}
def self.authenticate(key)
self.active.find_by_key(key)
end
def revoke!
self.revoked_at = Time.now
save!
end
def sudo?
sudo_enabled_at > 1.hour.ago
end
def sudo!
self.sudo_enabled_at = Time.now
save!
end
def access(request)
self.accessed_at = Time.now
self.ip = request.ip
self.user_agent = request.user_agent
save
end
private
def set_unique_key
self.key = SecureRandom.urlsafe_base64(32)
end
end
Staying true to the restful authentication spirit, SessionsController#create
creates a new UserSession
and SessionsController#destroy
deletes it.
A separate cookie called user_session
is set referencing the record unique random key. Only signed in users allocate this record. Anonymous traffic to GitHub never creates junk data in our sessions table.
We still have our signed cookie store around as session
in our controllers. This handles non-sensitive data like flash notices and multi-step form state. Then we have a separate user_session
helper that references the current user’s session record.
This infrastructure change took a few months. For a month, we ran both the old session code path on this new user session path at once. This allowed users to transition over to the new cookie without noticing.
Overall, we are pretty happy with the change. It has made our authentication logic much more clear and explicit. This opens up some new potential now that we have the data on the server.
Written by
Related posts
Students: Start building your skills with the GitHub Foundations certification
The GitHub Foundations Certification exam fee is now waived for all students verified through GitHub Education.
Announcing GitHub Secure Open Source Fund: Help secure the open source ecosystem for everyone
Applications for the new GitHub Secure Open Source Fund are now open! Applications will be reviewed on a rolling basis until they close on January 7 at 11:59 pm PT. Programming and funding will begin in early 2025.
Software is a team sport: Building the future of software development together
Microsoft and GitHub are committed to empowering developers around the world to innovate, collaborate, and create solutions that’ll shape the next generation of technology.