Starting a new Rails 4 : I. Authentication with Sorcery. bootstrap 3, rails 4, sorcery, bcrypt, flash message

Skeleton already in place :Gemfile

So I got

      gem 'rails', '4.0.0.beta1'
      gem 'sqlite3'
      gem 'thin'
      gem 'haml-rails' 

      group :development do
        gem 'pry-rails'
        gem 'looksee'
        gem 'awesome_print'
        gem 'hirb'
      end

      group :assets do
        gem 'sass-rails',   '~> 4.0.0.beta1'
        gem 'coffee-rails', '~> 4.0.0.beta1'
        gem 'uglifier', '>= 1.0.3'
      end

      gem 'jquery-rails'
      gem 'turbolinks'
      gem 'jbuilder', '~> 1.0.1'
      

Just add gem 'sorcery'.

Memo :

  • Thin is a better web server than the Webrick (default in Rails), and it work well with Nginx, even on windows.

  • haml-rails integrate Haml with Rails, and got some easily customizable templates.

  • pry-rails integrate pry in replacement of irb in Rails console

  • looksee, awesome_print and hirb are just some irb / pry helpers.

Nota : don’t use the standard bundle install on windows, but bundle install --local because some gem are specifically build from a Git source and not already pack in a gem. In example : SQlite3 for Ruby 2 …

Custom configuration file :

One thing I keep from old Rails version : the migration numeration … So

in config/application.rb

config.active_record.timestamped_migrations = false

Creating some pages :

For testing purpose, I want a home page, an about and a test page :

rails g controller home about index test

Change our routes to fit them :

config/routes.rb

root :to      => 'home#index'

get "login"   => "sessions#new",        :as => "login"
get "logout"  => "sessions#destroy",    :as => "logout"
get "signup"  => "users#new",           :as => "signup"

Scaffolding a User model :

Simply :

rails g scaffold user username:string lastname:string birthdate:date email:string active:boolean --no-stylesheets

The –no-stylesheets is for not generating a scaffold.css file, witch overwrite bootstrap css …

Authentication with Sorcery

For starting, I want a simple and robust authentication, there’re many choices, I had take Sorcery because it’s simplicity.

Some documentations :

Nota 1 : for Rails 4 you have to use the master branch

  git clone https://github.com/NoamB/sorcery.git
  gem build ./sorcery.gemspec
  gem uni sorcery
  gem i ./sorcery-0.8.1.gem

Nota 2 : if you got some errors about bcrypt, just recompile it :

  gem uni bcrypt-ruby
  gem i bcrypt-ruby --platform=ruby

So let’s start a new Git branch git br -b sorcery and install it (with all the modules here for testing, I use only the core and will implement the others after ) :

rails g sorcery:install # some help
rails g sorcery:install http_basic_auth external remember_me reset_password activity_logging brute_force_protection user_activation session_timeout 

Arrange your migration file (I drop all sorcery migration about user model in just one gist ) if you want and :

rake db:create
rake db:migrate

Sorcery Configuration

Check options in config/initializers/sorcery.rb Each module (remember me, etc …) have to be specified here. Starting with just the core module, I’ll add the others after. I put only this one and keep other default config :

    user.username_attribute_names = [:email]
    Rails.application.config.sorcery.submodules = [ :http_basic_auth ]
    

We have to declare also the user model as authenticate model in model/user.rb :

    class User < ActiveRecord::Base
      authenticates_with_sorcery!
     
      validates :password,                presence: true, confirmation: true, length: { minimum: 3}
      validates :email,                   presence: true, uniqueness: true
      validates :password_confirmation,   presence: true

    end
    

Then create a user in the Rails console :

    >user = User.new
    >user.username = "MyName"
    >user.email = "my@email.com"
    >user.password = "password"
    >user.save
    

Basic workflow

Welcome page (home#index)
  └──>  sign in ────────────────> session#new ──────> logged page
  └──>  sign up ──> user#new  ──────────────────────> Welcome page (not logged)
  └──>  logout  ────────────────> session#destroy ──> Welcome page

Already got home#index (welcome), home#about(will be out logged page) and home#test (will be protected for testing purpose).

Welcome Page

Simple (and brut css) welcome page, with some bootstrap stuff for the home#index view :

    .jumbotron{style: "margin-top:200px"}
      .h1 Welcome to Red !
      %br
      .h4 Test line H4
      %br
      = link_to 'Sign Up', login_path, :class => 'btn btn-large btn-primary'
      = link_to 'Login', login_path, :class => 'btn btn-large btn-success'
      - if current_user
        = link_to 'Logout', logout_path
    %br
    = link_to "About", about_path
    = link_to "Test", test_path
    

current_user will be avaible if the user is already logged in.

Control the access

All the pages are accessible for the moment. So, in application_controller.rb :

    before_filter :require_login, :except => [:not_authenticated]

    protected

    def not_authenticated
      redirect_to login_path, :alert => "Login requis." 
    end
    

And in the home\controller.rb :

    skip_before_filter :require_login, :only => [:index, :test]
    

Now, only the welcome and the test page will be viewable for non registered users.

Managing sessions

First, build the session controller :

rails g controller sessions new destroy

And :

    class SessionsController < ApplicationController

      before_action :set_user, only: [:create]
      before_action :require_login, only: [:destroy]

      def new
      end

      def create  
        if @user  
          redirect_back_or_to root_url , :notice => "Logged in!"  
        else  
          redirect_to root_url, :alert => "Email or password was invalid." 
        end  
      end 
      def destroy
        logout
        redirect_to root_url, :notice => 'Logged out!'
      end

      private
        def set_user
          @user = login(params[:sessions][:email], params[:sessions][:password])
        end

        def session_params
          params.require(:user).permit(:email, :password)
        end

    end
    

The logoutmethod is provided by Sorcery. Notice the new private methods introduced with Rails 4 (strong parameters) (with a declaration in the model include ActiveModel::ForbiddenAttributesProtection)

Nota : when you redirect, must have a root_url instead of root_path

Build our login form :

With use of bootstrap modal for testing ; the form will send a POST on session controller, witch redirect it to the create method : (in index.html.haml)

    #modal_login.modal.fade
      .modal-dialog
        .modal-content
          = form_for :sessions, :url => sessions_path, :html => {:class => 'form-horizontal'} do |f|
            .modal-header{style: "background-color: #EEEEEE;"}
              %button.close{:type => 'button', 'data-dismiss'=> 'modal', 'aria-hidden'=>"true", :style => "right:0px;"}= raw '&times;'
              %h2.modal-title Login
              %small Small text
            .modal-body
              .control-group
                = f.label :email, :class => 'control-label'
                .controls
                  = f.text_field :email
              .control-group
                = f.label :password, :class => 'control-label'
                .controls
                  = f.password_field :password
            .modal-footer{style: "background-color: #EEEEEE;"}
              .control-group
                .controls
                  = f.submit "Sign In", :class => "btn btn-sucess"
    

And call it with our signin button :

    = link_to 'Login', "#modal_login", :class => 'btn btn-large btn-success', "data-toggle" => "modal", "data-target" => "#modal_login"
    

So we’ve got a form pointing on sessions_path with POST method (so to the sessions create controller method ), with 2 parameters : [sessions][email] and [sessions][password].

Flash messages

Ok, before the last part (creating a new user), let’s have more informations with flash message.

Inspiration from Gist flash

So, in application_helper.rb

    def flash_class(level)
      case level
      when :notice then "alert alert-info"
      when :success then "alert alert-success"
      when :error then "alert alert-error"
      when :alert then "alert alert-block"
      else "alert #{level}"
      end
    end
    

Now define a shared partial view (in app/view/shared/_flash_message.html.haml) :

    - flash.each do |level, message|
      %div.fade.in{ :class => flash_class(level) }
        %a.close{:href => "#", "data-dismiss" => "alert",} ×
        %h4.alert-heading Notice !
        %p= message
    

And use it in our global layout : layouts/application.html.haml

    %body
      .container
        = render 'shared/flash_message'
        = yield
    

TIP : Auto hide Rails flash messages

Just drop this coffee script in your assets/javascript folder (look at http://reinteractive.net/posts/13-ui-tips-for-flash-messages-in-rails)

    $ ->
      flashCallback = ->
        $(".alert").fadeOut()
      $(".alert").bind 'click', (ev) =>
        $(".alert").fadeOut()
      setTimeout flashCallback, 3000
      

Sign up

Check some validations methods in user model and, same (brut) method, form pointing to the user controller this time.

    #modal_signup.modal.fade
      .modal-dialog
        .modal-content
          = form_for :user, :url => users_path, :html => {:class => 'form-horizontal'} do |f|
            .modal-header{style: "background-color: #EEEEEE;"}
              %button.close{:type => 'button', 'data-dismiss'=> 'modal', 'aria-hidden'=>"true", :style => "right:0px;"}= raw '&times;'
              %h2.modal-title SignUp
              %small Create your account
            .modal-body
              .control-group
                = f.label :email, :class => 'control-label'
                .controls
                  = f.text_field :email
              .control-group
                = f.label :password, :class => 'control-label'
                .controls
                  = f.password_field :password
              .control-group
                = f.label :password_confirmation, :class => 'control-label'
                .controls
                  = f.password_field :password_confirmation
              
            .modal-footer{style: "background-color: #EEEEEE;"}
              .control-group
                .controls
                  = f.submit "Sign Up", :class => "btn btn-primary"
    

Don’t forget to add password and password_confirmation to the user_params method (if not, you’ll got a nice blank value for this params) :

    def user_params
      params.require(:user).permit(:username, :lastname, :identity, :email, :birthdate, :active, :password, :password_confirmation)
    end
    

And that’s all for this time :) In case of some error, you will be redirected to the standard scaffold user page, created on starting.

Refactoring !!!

Next steps :

Mémo for sorcery

Mémo : From the core library, this is the avaible methods :

 # core
 require_login               # this is a before filter
 login(username,password,remember_me = false)
 auto_login(user)            # login without credentials
 logout
 logged_in?                  # available to view
 current_user                # available to view
  redirect_back_or_to         # used when a user tries to access a page while logged out, is asked to login, and we want to return him back to the page he originally wanted.
 @user.external?             # external users, such as facebook/twitter etc.
 User.authenticates_with_sorcery!

comments powered by Disqus