• Stars
    star
    234
  • Rank 171,630 (Top 4 %)
  • Language
    Ruby
  • License
    MIT License
  • Created about 10 years ago
  • Updated 10 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

👮‍♂️ Improve user session security in Ruby on Rails applications with database session storage

Authie

This is a Rails library which provides applications with a database-backed user sessions. This ensures that user sessions can be invalidated from the server and users activity can be easily tracked.

The "traditional" way of simply setting a user ID in your session is insecure and unwise. If you simply do something like the example below, it means that anyone with access to the session cookie can login as the user whenever and wherever they wish.

To clarify: while by default Rails session cookies are encrypted, there is nothing to allow them to be invalidated if someone were to "steal" an encrypted cookie from an authenticated user. This could be stolen using a MITM attack or simply by stealing it directly from their browser when they're off getting a coffee.

if user = User.authenticate(params[:username], params[:password])
  # Don't do this...
  session[:user_id] = user.id
  redirect_to root_path, :notice => "Logged in successfully!"
end

The design goals behind Authie are:

  • Any session can be invalidated instantly from the server without needing to make changes to remote cookies.
  • We can see who is logged in to our application at any point in time.
  • Sessions should automatically expire after a certain period of inactivity.
  • Sessions can be either permanent or temporary.

Installation

As usual, just pop this in your Gemfile:

gem 'authie', '~> 4.0'

You will then need add the database tables Authie needs to your database. You should copy Authie's migrations and then migrate.

rake authie:install:migrations
rake db:migrate

Usage

Authie is just a session manager and doesn't provide any functionality for your authentication or User models. Your User model should implement any methods needed to authenticate a username & password.

Creating a new session

When a user has been authenticated, you can simply set current_user to the user you wish to login. You may have a method like this in a controller.

class SessionsController < ApplicationController

  def create
    if user = User.authenticate(params[:username], params[:password])
      create_auth_session(user)
      redirect_to root_path
    else
      flash.now[:alert] = "Username/password was invalid"
    end
  end

end

Checking whether user's are logged in

On any subsequent request, you should make sure that your user is logged in. You may wish to implement a login_required controller method which is called before every action in your application.

class ApplicationController < ActionController::Base

  before_action :login_required

  private

  def login_required
    return if logged_in?

    redirect_to login_path, :alert => "You must login to view this resource"
  end

end

Accessing the current user (and session)

There are a few controller methods which you can call which will return information about the current session:

  • current_user - returns the currently logged in user
  • auth_session - returns the current auth session
  • logged_in? - returns a true if there's a session or false if no user is logged in

Catching session errors

If there is an issue with an auth session, an error will be raised which you need to catch within your application. The errors which will be raised are:

  • Authie::Session::InactiveSession - is raised when a session has been de-activated.
  • Authie::Session::ExpiredSession - is raised when a session expires.
  • Authie::Session::BrowserMismatch - is raised when the browser ID provided does not match the browser ID associated with the session token provided.
  • Authie::Session::HostMismatch - is raised when the session is used on a hostname that does not match that which created the session

The easiest way to rescue these to use a rescue_from. For example:

class ApplicationController < ActionController::Base

  rescue_from Authie::Session::ValidityError, :with => :auth_session_error

  private

  def auth_session_error
    redirect_to login_path, :alert => "Your session is no longer valid. Please login again to continue..."
  end

end

Logging out

In order to invalidate a session you can simply invalidate it.

def logout
  auth_session.invalidate
  redirect_to login_path, :notice => "Logged out successfully."
end

Default session length

By default, a session will last for however long it is being actively used in browser. If the user stops using your application, the session will last for 12 hours before becoming invalid. You can change this:

Authie.config.session_inactivity_timeout = 2.hours

This does not apply if the session is marked as persistent. See below.

Persisting sessions

In some cases, you may wish users to have a permanent sessions. In this case, you should ask users after they have logged in if they wish to "persist" their session across browser restarts. If they do wish to do this, just do something like this:

def persist_session
  auth_session.persist
  redirect_to root_path, :notice => "You will now be remembered!"
end

By default, persistent sessions will last for 2 months before requring the user logs in again. You can increase (or decrease) this if needed:

Authie.config.persistent_session_length = 12.months

Accessing all user sessions

If you want to provide users with a list of their sessions, you can access all active sessions for a user. The best way to do this will be to add a has_many association to your User model.

class User < ActiveRecord::Base
  has_many :sessions, :class_name => 'Authie::SessionModel', :as => :user, :dependent => :destroy
end

Storing additional data in the user session

If you need to store additional information in your database-backed database session, then you can use the following methods to achieve this:

auth_session.set :two_factor_seen_at, Time.now
auth_session.get :two_factor_seen_at

Invalidating all but current session

You may wish to allow users to easily invalidate all sessions which aren't their current one. Some applications invalidate old sessions whenever a user changes their password. The invalidate_others! method can be called on any Authie::Session object and will invalidate all sessions which aren't itself.

def change_password
  @user.change_password(params[:new_password])
  auth_session.invalidate_others!
end

Sudo functions

In some applications, you may want to require that the user has recently provided their password to you before executing certain sensitive actions. Authie provides some methods which can help you keep track of when a user last provided their password in a session and whether you need to prompt them before continuing.

# When the user logs into your application, run the see_password method to note
# that we have just seen their password.
def login
  if user = User.authenticate(params[:username], params[:password])
    create_auth_session(user, see_password: true)
    redirect_to root_path
  end
end

# Before executing any dangerous actions, check to see whether the password has
# recently been seen.
def change_password
  if auth_session.recently_seen_password?
    # Allow the user to change their password as normal.
  else
    # Redirect the user a page which allows them to re-enter their password.
    # The method here should verify the password is correct and call the
    # see_password method as above. Once verified, you can return them back to
    # this page.
    redirect_to reauth_path(:return_to => request.fullpath)
  end
end

By default, a password will be said to have been recently seen if it has been seen in the last 10 minutes. You can change this configuration if needed:

Authie.config.sudo_timeout = 30.minutes

Working with two factor authentication

Authie provides a couple of methods to help you determine when two factor authentication is required for a request. Whenever a user logs in and has enabled two factor authentication, you can mark sessions as being permitted.

You can add the following to your application controller and ensure that it runs on every request to your application.

class ApplicationController < ActionController::Base

  before_action :check_two_factor_auth

  def check_two_factor_auth
    if logged_in? && current_user.has_two_factor_auth? && !auth_session.two_factored?
      # If the user has two factor auth enabled, and we haven't already checked it
      # in this auth session, redirect the user to an action which prompts the user
      # to do their two factor auth check.
      redirect_to two_factor_auth_path
    end
  end

end

Then, on your two factor auth action, you need to ensure that you mark the auth session as being verified with two factor auth.

class LoginController < ApplicationController

  skip_before_action :check_two_factor_auth

  def two_factor_auth
    if user.verify_two_factor_token(params[:token])
      auth_session.mark_as_two_factored
      redirect_to root_path, :notice => "Logged in successfully!"
    end
  end

end

Storing IP address countries

Authie has support for storing the country that an IP address is located in whenever they are saved to the database. To use this, you need to specify a backend to use in the Authie configuration. The backend should respond to #call(ip_address).

Authie.config.lookup_ip_country_backend = proc do |ip_address|
  SomeService.lookup_country_from_ip(ip_address)
end

Instrumentation/Notification

Authie will publish events to the ActiveSupport::Notification instrumentation system. The following events are published with the given attributes.

  • set_browser_id.authie - when a new browser ID is set for a user. Provides :browser_id and :controller arguments.
  • cleanup.authie - when session cleanup is run. Provides no arguments.
  • touch.authie - when a session is touched. Provides :session argument.
  • see_password.authie - when a session sees a password. Provides :session argument.
  • mark_as_two_factor.authie - when a session has two factor credentials provided. Provides :session argument.
  • session_start.authie - when a session is started. Provides :session argument.
  • session_invalidate.authie - when a session is intentionally invalidated. Provides :session argument with session model instance.
  • browser_id_mismatch_error.authie - when a session is validated when the browser ID does not match. Provides :session argument.
  • invalid_session_error.authie - when a session is validated when invalid. Provides :session argument.
  • expired_session_error.authie - when a session is validated when expired. Provides :session argument.
  • inactive_session_error.authie - when a session is validated when inactive. Provides :session argument.
  • host_mismatch_error.authie - when a session is validated and the host does not match. Provides :session argument.

Differences for Authie 4.0

Authie 4.0 introduces a number of changes to the library which are worth noting when upgrading from any version less than 4.

  • Authie 4.0 removes the impersonation features which may make a re-appearance in a futre version.
  • All previous callback/events have been replaced with standard ActiveSupport instrumentation notifications.
  • Authie::SessionModel has been introduced to represent the instance of the underlying database record.
  • Various methods on Authie::Session (more commonly known as auth_session) have been renamed as follows.
    • check_security! is now validate
    • persist! is now persist
    • invalidate! is now invalidate
    • touch! is now touch
    • set_cookie! is now set_cookie and is now a private method and should not be called directly.
    • see_password! is now see_password
    • mark_as_two_factored! is now mark_as_two_factored
  • A new Authie::Session#reset_token has been added which will generate a new token for a session, save it and update the cookie.
  • When starting a session using Authie::Session.start or create_auth_session you can provide the following additional options:
    • persistent: true to mark the session as persistent (i.e. give it an expiry time)
    • see_password: true to set the password seen timestamp at the same time as creation
  • If the extend_session_expiry_on_touch config option is set to true (default is false), the expiry time for a persistent session will be extended whenver a session is touched.
  • When making a request, the session will be touched after the action rather than before. Previously, the touch_auth_session method was added before every action and it both validated the session and touched it. Now, there are two separate methods - validate_auth_session which is run before every action and touch_auth_session runs after every action. If you don't want to touch a session in a request you can either use skip_around_action :touch_auth_session or call skip_touch_auth_session! anywhere in the action.
  • A new config option called session_token_length is available which allows you to change the length of the random token used for sessions (default 64).

More Repositories

1

staytus

💡 An open source solution for publishing the status of your services
Ruby
2,169
star
2

procodile

🐊 Run processes in the background (and foreground) on Mac & Linux from a Procfile (for production and/or development environments)
Ruby
616
star
3

documentation

A Rails engine to provide the ability to add documentation to a Rails application
Ruby
214
star
4

vat-rates

172
star
5

fake-person

Create some fake personalities
Ruby
116
star
6

rails-safe-tasks

Automatically disable dangerous Rake tasks in production
Ruby
103
star
7

memist

A Ruby Memoization Helper
Ruby
76
star
8

json-vat

A Ruby client library for jsonvat.com
Ruby
69
star
9

ghost-theme

The theme used on my Ghost blog at http://blog.adamcooke.io
CSS
59
star
10

florrick

A Rails extension for providing awesome user-initiated string interpolation
Ruby
57
star
11

key-installer

Installs SSH public keys on remote hosts from a single command
Ruby
49
star
12

moonrope

A library to help with building an RPC-like JSON API
Ruby
34
star
13

apns-proxy

A proxy for sending requests to the Apple Push Notification Service
Ruby
21
star
14

muck

A tool for backing up remote MySQL databases
Ruby
18
star
15

runa

A redis-powered job/background runner
Ruby
17
star
16

changey

Run callbacks based on changes to attributes in Active Record
Ruby
17
star
17

param-auto-permit

Automatically permit attributes which are present in the sending form
Ruby
15
star
18

mysql-http-api

An HTTP API to any MySQL Database
Ruby
12
star
19

riptables

A Ruby DSL for generating iptables configuration
Ruby
12
star
20

send_file_with_range

A Rails addition which will allow sending of files with appropriate range headers
Ruby
12
star
21

budgets

A little Rails app to help manage your finances
Ruby
12
star
22

keyman

A utility for managing distributed authorized keys
Ruby
11
star
23

actionnav

🗺 A navigation manager for Rails apps
Ruby
11
star
24

seeka

An Active Record Search Interface
Ruby
10
star
25

railswork

Source for www.railswork.com
Ruby
9
star
26

procodile-capistrano

Capistrano 2 and 3 recipes for Procodile
Ruby
9
star
27

checken

🐓 An authorization framework for Ruby & Rails applications
Ruby
9
star
28

myxi

A web socket server for Ruby with RabbitMQ
Ruby
9
star
29

geolocate

A Ruby library to return information about a given IP (currently using ip-api.com)
Ruby
9
star
30

nissh

A wrapper for net/ssh to allow easy command execution
Ruby
9
star
31

datey

A Ruby library for interrogating and formatting dates
Ruby
9
star
32

hmrc-moss-return

A Ruby library to generate HMRC VAT MOSS Return files
Ruby
8
star
33

authorized_networks

🔐 Easily restrict access to networks based on source IP
Ruby
8
star
34

twitterfications

A Rails plugin which allows you to send tweets to alert you of specific activities within your app with considerable ease
Ruby
8
star
35

procman

A very simple process manager for Ruby apps
Ruby
8
star
36

rails-template

My template for creating new Rails applications
Ruby
8
star
37

parameter_sets

🍔 A friendly schema for defining permitted parameters in Rails controllers
Ruby
8
star
38

color_fun

A little gem for doing stuff with colors
Ruby
7
star
39

apiable-model-errors

Provide ActiveModel errors in an format suitable for API consumers
Ruby
6
star
40

lizard

🏞 Very simple ImageMagick interface for Ruby
Ruby
6
star
41

sqb

👷‍♀️ A very simple but effective SQL query builder
Ruby
6
star
42

moss-return-api

An API to generate HMRC VAT MOSS Return ODS files from JSON
Ruby
6
star
43

log_logins

🖍 A small library to log login attempts & block after too many failed attempts
Ruby
6
star
44

rails-env-config

Set environment variables for your local development
Ruby
5
star
45

sshake

🤝A friendly SSH interface on top of net/ssh
Ruby
5
star
46

hs1xx

Control TP-Link HS100/HS110 devices from Ruby
Ruby
5
star
47

apns-key-convert

A script to create PEM files from P12 and CER files.
Ruby
4
star
48

dasher-ruby

A Ruby library for Dasher
Ruby
4
star
49

ipgeo-server

Simple HTTP interface to a MaxMind IP database
Ruby
4
star
50

waiter

⏳ A tiny application that waits for a set of services to become available before exiting clean. Ideal for init containers.
Go
4
star
51

jobster

🦐 A RabbitMQ-based job queueing system for lobsters and absolute pros.
Ruby
4
star
52

growler

A simple codebase/github post-receive script to send a growl notification for each commit to a group of networked machines whenever a push is received.
PHP
4
star
53

chuck-divides-by-zero

Ruby
3
star
54

potamus

Utility for building and publishing Docker images
Ruby
3
star
55

apns-client

A client library for APNS Proxy
Ruby
3
star
56

plesk

Plesk for Ruby
Ruby
3
star
57

hippo

Deployment orchestration for Kubernetes
Ruby
3
star
58

rubysnippets

Sublime Text 3 snippets for Ruby & Rails
3
star
59

secure_random_string

A random string generator
Ruby
3
star
60

moonrope-client

A client library for the Moonrope server
Ruby
3
star
61

maxminder

Ruby library for the MaxMind minFraud checking service
Ruby
3
star
62

uu

Some useful things for your Rails applications
Ruby
3
star
63

bluebird

🐦 A TUI for quickly accessing a library of pre-defined commands
Go
3
star
64

bask-proxy

🐊🐊🐊 Manage multiple procodiles on your development environment
Ruby
2
star
65

moonrope-workbench

A graphical frontend to use when testing Moonrope APIs
CSS
2
star
66

shoppe

The original Shoppe
Ruby
2
star
67

ipgeo-ruby

A Ruby library for talking to the IPGeo Server
Ruby
2
star
68

uk_vat_rate

Ruby
2
star
69

rack-custom-proxies

Allow the list of trusted proxies to be set by environment variable
Ruby
2
star
70

ghost-theme-2015

My blog's Ghost theme
CSS
2
star
71

LlamaKit

Some bits and bobs for iOS/tvOS development
Swift
1
star
72

cloudapp-exporter

Ruby
1
star
73

notes

1
star
74

llama-app

An empty app to use a skeleton for new Rails applications
Ruby
1
star
75

gandi

Gandi Domain Registration Library for Ruby
Ruby
1
star
76

moonrope-oc-sdk

Moonrope Objective C SDK
Objective-C
1
star
77

documentation-elasticsearch

An Elasticsearch module for Documentation
Ruby
1
star
78

webnull

A web server that accepts requests and returns the status you want
Ruby
1
star
79

swamp

A framework for writing simple CLI applications
Ruby
1
star
80

swipe

A Javascript App Framework
JavaScript
1
star
81

ripe-db

A Ruby library for talking to the RIPE datbaase
Ruby
1
star
82

localresolv

A DNS server which returns 127.0.0.1 for any domain you provide
Ruby
1
star
83

moonrope-workbench-osx

An OS X Application Wrapper for the Moonrope Workbench Tool
Objective-C
1
star
84

ACToolkit

A set of useful tools which I used when developing iOS applications
Objective-C
1
star
85

records-manipulator

Add manipulations to an Active Record scope to change the records when they are finally returned from the database
Ruby
1
star
86

monograph

A tool to help writers write content in Markdown and easily export into an HTML site.
Ruby
1
star
87

imap-gmail-importer

Ruby
1
star
88

advent-of-code

Ruby
1
star