Totally Authentic(ate):

Devise and Can-Can

+

What is authentication?

  • Registration
  • Login
  • Change Password
  • Forgot Password

Gem'ing up the works

First step is to add Devise to your Gemfile

But we're not done just yet...

And the run bundle install, of course...


gem 'devise'
$ bundle install

Installing Devise

In your Terminal/Command Prompt run:

$ rails g devise:install

So just like that devise is installed, right?

Devise a model

We need to tell Devise, in our database, where to store all this fun authentication stuff: email, password, name.

Don't forget get to rake db:migrate!

$ rails g devise Blogger

+

=

What'd that do?

You can now go in your app's code and find a model (blogger.rb), that will look like this:

class Blogger < ActiveRecord::Base

  # Include default devise modules. Others available are:

  # :confirmable, :lockable, :timeoutable and :omniauthable

  devise :database_authenticatable, :registerable,

  :recoverable, :rememberable, :trackable, :validatable

end

Welcome, blogger

Set up your root page so that it allows you to sign in, or knows that you're already signed in.

<h1>Welcome#index</h1>

<p>Find me in app/views/welcome/index.html.erb</p>



<% if blogger_signed_in? %>

  <%= link_to "Sign out",destroy_blogger_session_path, method: :delete, class: "button" %>

<% else %>

  <%= link_to "Sign in",new_blogger_session_path %>

<% end %>

who's signed in anyway?

You now have access to the Class 'current_blogger'.

<h1>Welcome#index</h1>

<p>Find me in app/views/welcome/index.html.erb</p>



<% if blogger_signed_in? %>

  Hello, <%= current_blogger.email %>!<br />

  <%= link_to "Sign out",destroy_blogger_session_path, method: :delete, class: "button" %>

<% else %>

  <%= link_to "Sign in",new_blogger_session_path %>

<% end %>

Devise views

We can customize the sign-in/sign-up forms.

 

First we need to create the views for Devise.

$ rails g devise:views

 A new folder has been created, under 'views', called 'devise', and there's a whole bunch of stuff in there!

A Blogger by any other name...

Devise gives you Email and Password, but wouldn't it be great to have Name, too?

 

We can add attributes to the User just as we would to any Resource.

$ rails g migration AddNameToBloggers name:string

And there's one more thing we can't forget to run...

  #add the following lines:

  before_action :configure_permitted_parameters, if: :devise_controller?



  protected



  def configure_permitted_parameters

    devise_parameter_sanitizer.for(:sign_up) { |u| u.permit({ roles: [] }, :email, :password,
    :password_confirmation, :name) }

    devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:email, :password, 
    :password_confirmation, :current_password, :name) }

  end

Since we have no Blogger controller, we'll need the help of the Application controller...

application controller finally becomes useful!

Change your view

We need to also add fields to our devise/registration views (edit & new), so a name can be entered.


  <div><%= f.label :name %><br />
    <%= f.text_field :name, autofocus: true %>
  </div>

autofocus: true means that field will be automatically selected when the page is loaded. I moved this attribute from the e-mail field.

Change your view

Let's use Bootstrap to fancy-up these Devise views...

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <h2>Log in</h2>
      <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
        <div class="input-group input-group-lg">
          <%= f.email_field :email, autofocus: true, placeholder: "Email", class: "form-control" %>
	</div>
	<br/>
	<div class="input-group input-group-lg">
	   <%= f.password_field :password, autocomplete: "off", placeholder: "Password", class: "form-control" %>
	</div>
        <% if devise_mapping.rememberable? -%>
	    <div><%= f.check_box :remember_me %> <%= f.label :remember_me %></div>
        <% end -%>
	<br/>
        <div><%= f.submit "Log in", class: "btn btn-primary btn-lg"%></div>
      <% end %>
     <%= render "devise/shared/links" %>
  </div>
</div>

devise/sessions/new.html.erb

That seems glyphy

Let's add some glyphicons to add that extra bit of flair!

 <span class="input-group-addon glyphicon glyphicon-envelope"></span>
     
 <span class="input-group-addon glyphicon glyphicon-lock" ></span>
   

Let's remove the partial link reference. How could we add back just the "forgot password?" link under the password field?

Final sign-in page code:

<div class="row">
  <div class="col-md-6 col-md-offset-3">
     <h2>Log in</h2>
       <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
            		  
       <div class="input-group input-group-lg">
         <span class="input-group-addon glyphicon glyphicon-envelope"></span>
         <%= f.email_field :email, autofocus: true, placeholder: "Email", class: "form-control" %>
       </div>
       <br/>
       
       <div class="input-group input-group-lg">
         <span class="input-group-addon glyphicon glyphicon-lock" ></span>
         <%= f.password_field :password, autocomplete: "off", placeholder: "Password", class: "form-control" %>
       </div>
        <% if devise_mapping.rememberable? -%>
	    <div><%= f.check_box :remember_me %> <%= f.label :remember_me %></div>
        <% end -%>
       <p class="pull-right">
         <%= link_to "Forgot Password?", new_blogger_password_path %>
       </p>
       <br/>
       <div class="input-group input-group-lg"> 
         <%= f.submit "Log in", class: "btn btn-primary btn-lg"%>
       </div>
            
       <% end %>
  </div>
</div>

Your view is changed

Controller Filter

Let's require users to log in before entering any page of our app. We'll add the following filter to our application controller:

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  before_action :authenticate_blogger!

  #all your other devise stuff is below
...

Where would we move this filter if we wanted the home page to be available to all users, but the destinations page behind the login?

one

two

three

break!

devising your travel app

Let's add Devise to our Travel app.

Since you already have a Traveler resource, let's call the Devise resource User.

Some soothing sounds as you work...

do the CanCan

Let's add a new gem called 'cancan.' This will allow us to control user roles so that only admins can edit content.

Add the gem to your gemfile and bundle!


gem 'cancan'

$ bundle install

You CanCan

We now create a model specific to cancan. 

$ rails g cancan:ability

Now we set roles. Admins can manage all content.

app/models/ability.rb

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # guest user (not logged in)
    if user.admin?
      can :manage, :all
    else
      can :read, :all
    end
  end
end

Remove Crud buttons

for non-admins

Admins should be the only ones allowed to create, update, and delete (CUD?) on our destinations page. So let's add some logic around our button links in destination/index.html.erb:

 <% if can? :update, destination %>
   <div class="btn-group">
     <%= link_to 'Show', destination, class: "btn btn-default" %>
     <%= link_to 'Edit', edit_destination_path(destination), class: "btn btn-default" %>
     <%= link_to 'Destroy', destination, method: :delete, data: { confirm: 'Are you sure?' }, 
     class: "btn btn-default" %>
   </div>
 <% end %>

And don't forget the new destination button!

<% if can? :update, @destination %>
  <%= link_to 'New Destination', new_destination_path, class: "btn btn-primary" %>
<% end %>

Add Admin

This all fine and good, but we don't actually have an admin column defined in our database. So let's add one.


$ rails g migration AddAdminToUsers admin:boolean



Now let's set admin to be true for the blogger of our choice using rails console. Blogger.all will display all bloggers in the db.

$ rails c
> User.all

!

Add Admin

Make sure you have at least 2 users in your database for testing purposes. We'll set one of them to have "true" in their admin field. Find the id of the user you'd like to promote to top dog!

Now try logging in with your admin user and navigating to the destinations page. Notice your edit buttons are in place. Log in with a regular non-admin user and notice the buttons are missing.

> admin_guy = User.find(1)
> admin_guy.admin = "true"
> admin_guy.save

Authorization

In order to automatically authorize all actions on the destinations page we must add this line to the top of the destinations_controller.rb:

load_and_authorize_resource

We'll also need this method at the bottom of the 

application_controller.rb to redirect in case of an exception:

rescue_from CanCan::AccessDenied do |exception|
    redirect_to destinations_path, :alert => exception.message
  end

And output the exception message on  destinations/show.html.erb:

<% if alert %>
    <div id="notice" class="alert alert-danger" role="alert"><%= alert %></div>
<% end %>

Authorization

Finally, we'll output the exception message on destinations/index.html.erb:

<% if alert %>
    <div id="notice" class="alert alert-danger" role="alert"><%= alert %></div>
<% end %>

Go ahead and test this out. Try to browse to "destinations/new" with a non-admin user logged in. The user should be re-routed to the destination index page with error message that says:

 

"You are not authorized to access this page."

Devise & Can-Can

By argroch

Devise & Can-Can

Using the Devise and Can-Can gems in the projects of the TTS part-time class.

  • 1,371