Starting a new Rails 4 : I. Authentication with Sorcery. bootstrap 3, rails 4, sorcery, bcrypt, flash message
- Skeleton already in place :Gemfile
- Custom configuration file :
- Creating some pages :
- Scaffolding a User model :
- Authentication with Sorcery
- Sorcery Configuration
- Basic workflow
- Welcome Page
- Control the access
- Managing sessions
- Build our login form :
- Flash messages
- TIP : Auto hide Rails flash messages
- Sign up
- Refactoring !!!
- Mémo for sorcery
Skeleton already in place :Gemfile
So I got
- bootsrap 3 installed
- a custom less configuration file (assests/less/main-custom.less)
- some basics gem in gemfile
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, butbundle 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 :
- https://github.com/NoamB/sorcery
- http://asciicasts.com/episodes/283-authentication-with-sorcery
- http://stackoverflow.com/questions/tagged/sorcery
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 logout
method 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 '×'
%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 '×'
%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 :
- create partial for both modal dialogs
- use simple_form gem for clearing
- create a semantic css declaration
Mémo for sorcery
Mémo : From the core library, this is the avaible methods :
Tweet# 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