Skip to content
This repository
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 543 lines (398 sloc) 13.304 kb

Clearance

Build Status Code Climate Dependency Status

Rails authentication with email & password.

Clearance was extracted out of Airbrake. It is intended to be small, simple, and well-tested. It is intended to be easy to override defaults.

Use GitHub Issues for help.

Read CONTRIBUTING.md to contribute.

Install

Clearance is a Rails engine tested against Rails >= 3.2 and Ruby >= 1.9.3. It works with Rails 4 and Ruby 2.

Include the gem in your Gemfile:

gem 'clearance'

Bundle:

bundle install

Make sure the development database exists. Then, run the generator:

rails generate clearance:install

The generator:

  • inserts Clearance::User into your User model
  • inserts Clearance::Controller into your ApplicationController
  • creates a migration that either creates a users table or adds only missing columns

Then, follow the instructions output from the generator.

Use Clearance 0.8.8 for Rails 2 apps.

Use Clearance 0.16.3 for Ruby 1.8.7 apps.

Configure

Override any of these defaults in config/initializers/clearance.rb:

Clearance.configure do |config|
  config.allow_sign_up = true
  config.cookie_domain = '.example.com'
  config.cookie_expiration = lambda { |cookies| 1.year.from_now.utc }
  config.cookie_path = '/'
  config.httponly = false
  config.mailer_sender = 'reply@example.com'
  config.password_strategy = Clearance::PasswordStrategies::BCrypt
  config.redirect_url = '/'
  config.secure_cookie = false
  config.sign_in_guards = []
  config.user_model = User
end

Use

Use current_user, signed_in?, and signed_out? in controllers, views, and helpers. For example:

- if signed_in?
  = current_user.email
  = link_to 'Sign out', sign_out_path, method: :delete
- else
  = link_to 'Sign in', sign_in_path

To authenticate a user elsewhere than sessions/new (like in an API):

User.authenticate 'email@example.com', 'password'

When a user resets their password, Clearance delivers them an email. So, you should change the mailer_sender default, used in the email's "from" header:

Clearance.configure do |config|
  config.mailer_sender = 'reply@example.com'
end

Use authorize to control access in controllers:

class ArticlesController < ApplicationController
  before_filter :authorize

  def index
    current_user.articles
  end
end

Or, you can authorize users in config/routes.rb:

Blog::Application.routes.draw do
  constraints Clearance::Constraints::SignedIn.new { |user| user.admin? } do
    root to: 'admin/dashboards#show', as: :admin_root
  end

  constraints Clearance::Constraints::SignedIn.new do
    root to: 'dashboards#show', as: :signed_in_root
  end

  constraints Clearance::Constraints::SignedOut.new do
    root to: 'marketing#index'
  end
end

Clearance adds its session to the Rack environment hash so middleware and other Rack applications can interact with it:

class Bubblegum::Middleware
  def initialize(app)
    @app = app
  end

  def call(env)
    if env[:clearance].signed_in?
      env[:clearance].current_user.bubble_gum
    end

    @app.call(env)
  end
end

Overriding routes

See config/routes.rb for the default behavior.

To override a Clearance route, redefine it:

resource :session, controller: 'sessions'

Overriding controllers

See app/controllers/clearance for the default behavior.

To override a Clearance controller, subclass it:

class PasswordsController < Clearance::PasswordsController
class SessionsController < Clearance::SessionsController
class UsersController < Clearance::UsersController

Then, override public methods:

passwords#create
passwords#edit
passwords#new
passwords#update
sessions#create
sessions#destroy
sessions#new
users#create
users#new

Or, override private methods:

passwords#find_user_by_id_and_confirmation_token
passwords#find_user_for_create
passwords#find_user_for_edit
passwords#find_user_for_update
passwords#flash_failure_after_create
passwords#flash_failure_after_update
passwords#flash_failure_when_forbidden
passwords#forbid_missing_token
passwords#forbid_non_existent_user
passwords#url_after_create
passwords#url_after_update
sessions#url_after_create
sessions#url_after_destroy
users#flash_failure_after_create
users#url_after_create
users#user_from_params
users#user_params

All of these controller methods redirect to '/' by default:

passwords#url_after_update
sessions#url_after_create
users#url_after_create
application#url_after_denied_access_when_signed_in

To override them all at once, change the global configuration:

Clearance.configure do |config|
  config.redirect_url = '/overriden'
end

Overriding translations

All flash messages and email subject lines are stored in i18n translations. Override them like any other translation.

See config/locales/clearance.en.yml for the default behavior.

Overriding views

See app/views for the default behavior.

To override a view, create your own:

app/views/clearance_mailer/change_password.html.erb
app/views/passwords/create.html.erb
app/views/passwords/edit.html.erb
app/views/passwords/new.html.erb
app/views/sessions/_form.html.erb
app/views/sessions/new.html.erb
app/views/users/_form.html.erb
app/views/users/new.html.erb

There is a shortcut to copy all Clearance views into your app:

rails generate clearance:views

Overriding the model

See lib/clearance/user.rb for the default behavior.

To override the model, redefine public methods:

User.authenticate(email, password)
User#forgot_password!
User#reset_remember_token!
User#update_password(new_password)

Or, redefine private methods:

User#email_optional?
User#generate_confirmation_token
User#generate_remember_token
User#normalize_email
User#password_optional?

Overriding the password strategy

By default, Clearance uses BCrypt encryption of the user's password.

See lib/clearance/password_strategies/bcrypt.rb for the default behavior.

Change your password strategy in config/initializers/clearance.rb:

Clearance.configure do |config|
  config.password_strategy = Clearance::PasswordStrategies::SHA1
end

Clearance provides the following strategies:

Clearance::PasswordStrategies::BCrypt
Clearance::PasswordStrategies::BCryptMigrationFromSHA1
Clearance::PasswordStrategies::Blowfish
Clearance::PasswordStrategies::SHA1

The previous default password strategy was SHA1.

Switching password strategies may cause your existing users to not be able to sign in.

If you have an existing app that used the old SHA1 strategy and you want to stay with SHA1, use Clearance::PasswordStrategies::SHA1.

If you have an existing app that used the old SHA1 strategy and you want to switch to BCrypt transparently, use Clearance::PasswordStrategies::BCryptMigrationFromSHA1.

The SHA1 and Blowfish password strategies require an additional salt column in the users table. Run this migration before switching to SHA or Blowfish:

class AddSaltToUsers < ActiveRecord::Migration
  def change
    add_column :users, :salt, :string, limit: 128
  end
end

You can write a custom password strategy that has two instance methods. In your authenticated? method, encrypt the password with your desired strategy, and then compare it to the encrypted_password that is provided by Clearance.

module CustomPasswordStrategy
  def authenticated?(password)
    encrypted_password == encrypt(password)
  end

  def password=(new_password)
  end

  private

  def encrypt
    # your encryption strategy
  end
end

Clearance.configure do |config|
  config.password_strategy = CustomPasswordStrategy
end

Deliver password reset email in a background job

Clearance has one mailer. It is used to reset the user's password.

To deliver it in a background job using a queue system like Delayed Job, subclass Clearance::PasswordsController and define the behavior you need in its deliver_email method:

class PasswordsController < Clearance::PasswordsController
  def deliver_email(user)
    ClearanceMailer.delay.change_password(user)
  end
end

Then, override the route:

resources :passwords, only: [:create]

Using the SignInGuard stack

SignInGuards offer fine-grained control over the process of signing in a user. Each guard is run in order and hands the session off to the next guard in the stack.

A SignInGuard is an object that responds to call. It is initialized with a session and the current stack.

On success, a guard should call the next guard or return SuccessStatus.new if you don't want any subsequent guards to run.

On failure, a guard should call FailureStatus.new(failure_message). It can provide a message explaining the failure.

For convenience, a SignInGuard class has been provided and can be inherited from. The convenience class provides a few methods to help make writing guards simple: success, failure, next_guard, signed_in?, and current_user.

Here's an example custom guard to handle email confirmation:

Clearance.configure do |config|
  config.sign_in_guards = [EmailConfirmationGuard]
end
class EmailConfirmationGuard < Clearance::SignInGuard
  def call
    if unconfirmed?
      failure("You must confirm your email address.")
    else
      next_guard
    end
  end

  def unconfirmed?
    signed_in? && !current_user.confirmed_at
  end
end

Optional feature specs

You can generate feature specs to help prevent regressions in Clearance's integration with your Rails app over time.

Edit your Gemfile to include the dependencies:

gem 'capybara', '~> 2.0'
gem 'factory_girl_rails', '~> 4.2'
gem 'rspec-rails', '~> 2.13'

Generate RSpec files:

rails generate rspec:install

Generate Clearance specs:

rails generate clearance:specs

Run the specs:

rake

Testing authorized controller actions

To test controller actions that are protected by before_filter :authorize, require Clearance's test helpers and matchers in your test suite.

For rspec, add this line to your spec/spec_helper.rb:

require 'clearance/rspec'

For test-unit, add this line to your test/test_helper.rb:

require 'clearance/test_unit'

This will make Clearance::Controller methods work in your controllers during functional tests and provide access to helper methods like:

sign_in
sign_in_as(user)
sign_out

And matchers like:

deny_access

Example:

context 'a guest' do
  before do
    get :show
  end

  it { should deny_access }
end

context 'a user' do
  before do
    sign_in
    get :show
  end

  it { should respond_with(:success) }
end

You may want to customize the tests:

it { should deny_access  }
it { should deny_access(flash: 'Denied access.')  }
it { should deny_access(redirect: sign_in_url)  }

Faster tests

Clearance includes middleware that avoids wasting time spent visiting, loading, and submitting the sign in form. It instead signs in the designated user directly. The speed increase can be substantial.

Configuration:

# config/environments/test.rb
MyRailsApp::Application.configure do
  # ...
  config.middleware.use Clearance::BackDoor
  # ...
end

Usage:

visit root_path(as: user)

Credits

thoughtbot

Clearance is maintained by thoughtbot, inc and contributors like you. Thank you!

License

Clearance is copyright © 2009-2014 thoughtbot. It is free software, and may be redistributed under the terms specified in the LICENSE file.

The names and logos for thoughtbot are trademarks of thoughtbot, inc.

Something went wrong with that request. Please try again.