Author avatar

stockholmkid

Implementing a Custom Devise Sign in and Actioncable Rails 5

stockholmkid

  • Jan 10, 2019
  • 8 Min read
  • 21,335 Views
  • Jan 10, 2019
  • 8 Min read
  • 21,335 Views
Ruby
Ruby on Rails

Introduction

In this tutorial, we'll be creating a Rails 5 App that uses a custom Devise log in page with ActionCable! What makes this app so unique is that we want to define a subscribed user in our connection channel to be the current_user that signs in. I will walk you through the steps of completing this challenge.

Together, Devise and ActionCable will allow us to track a user's actions, specifically in a game setting. This can later let us create a game lobby, see users' past scores, and determine who is #1 on the leaderboards.

Installation

Let's get started.

First make sure that you have Rails up to date. Use: rails -v and make sure the output matches this: Rails 5.0.0.rc1

Once you have the most up-to-date version of Rails, create a new Rails 5 app:

1rails new <Your App Name>
bash

Next include the Devise Gem File:

1gem 'devise'
ruby

Bundle:

1bundle install
bash

Next install the Devise basics, along with the Devise Views:

1rails generate devise:install && rails generate devise:views
bash

Now it is time to create a User model with Devise

1rails generate devise <Your Model Name, Usually Just: user>
bash

Finally we need to migrate and rollback our Devise table:

1rake db:migrate && rake db:rollback && rake db:migrate
bash

Congrats! You have successfully created a Devise model.

Application

But now we want to add in a custom sign in such as studentID instead of email. For this example we will use: studentid

We are first going to add authentication to the User Model so we can allow studentid to be the verification for signing a user in by adding :authentication_keys:

1class User < ApplicationRecord
2
3  # Include default devise modules. Others available are:
4  # :confirmable, :lockable, :timeoutable and :omniauthable
5  validates :studentid, numericality: { only_integer: true}, presence: true, length: { is: 6 }
6
7  devise :database_authenticatable, :registerable,
8         :recoverable, :rememberable, :trackable, :validatable, :authentication_keys => [:studentid]
9end
ruby

Next we are going to go into our ApplicationController to permit studentid inputs when signing in, as well as other devise attributes:

1class ApplicationController < ActionController::Base
2  # Prevent CSRF attacks by raising an exception.
3  # For APIs, you may want to use :null_session instead.
4  protect_from_forgery with: :exception
5  before_action :configure_permitted_parameters, if: :devise_controller?
6
7  protected
8
9  def after_sign_in_path_for(users)
10    grid_index_path
11  end
12
13
14  def configure_permitted_parameters
15    devise_parameter_sanitizer.permit(:sign_up) { |u| u.permit(:studentid, :email, :password, :password_confirmation, :remember_me) }
16    devise_parameter_sanitizer.permit(:sign_in) { |u| u.permit(:studentid, :password, :remember_me) }
17    devise_parameter_sanitizer.permit(:account_update) { |u| u.permit( :studentid, :email, :password, :password_confirmation, :current_password) }
18  end
19
20end
ruby

def after_sign_in_path_for(users) will allow you to redirect users after sign-in in to your home page. In this case my home page is: grid_index_path

We will now need to include this in our routes.rb file.

1Rails.application.routes.draw do
2  devise_for :users, controllers: { registrations: "registrations"}
3  as :user do
4    get '/' => 'devise/registrations#new'
5  end
6  mount ActionCable.server => "/cable"
7  resources :grid
8end
ruby

Here we will specify that upon visiting our site a user will be sent to creating a new account via 'devise/registrations#new'. You can send them other places as well by changing the route. For now ignore the Action Cable Mount but we will need that for later!

Next we'll go into our Views for Devise to add our custom sign in.

First, change the new.html.erb file in views/devise/registrations by adding in a field for our new custom sign in:

1<div class="field">
2    <%= f.label :studentid, 'Colorado College ID' %><br />
3    <%= f.text_field :studentid, autofocus: true %>
4  </div>
jsx

The full file will now look like this:

1<h2>Sign up</h2>
2
3<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
4  <%= devise_error_messages! %>
5
6  <div class="field">
7    <%= f.label :studentid, 'Colorado College ID' %><br />
8    <%= f.text_field :studentid, autofocus: true %>
9  </div>
10  <br>
11  <div class="field">
12    <%= f.label :email %><br />
13    <%= f.email_field :email%>
14  </div>
15  <br>
16  <div class="field">
17    <%= f.label :password %>
18    <% if @minimum_password_length %>
19    <em>(<%= @minimum_password_length %> characters minimum)</em>
20    <% end %><br />
21    <%= f.password_field :password, autocomplete: "off" %>
22  </div>
23  <br>
24
25  <div class="field">
26    <%= f.label :password_confirmation %><br />
27    <%= f.password_field :password_confirmation, autocomplete: "off" %>
28  </div>
29  <br>
30  <div class="actions">
31    <%= f.submit "Sign up" %>
32  </div>
33<% end %>
34
35<%= render "devise/shared/links" %>
jsx

We will now also need to change the file new.html.erb file in views/devise/sessions

1<h2>Log in</h2>
2
3<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
4  <div class="field">
5    <%= f.label :studentid, 'Colorado College ID' %><br />
6    <%= f.text_field :studentid, autofocus: true %>
7  </div>
8
9  <div class="field">
10    <%= f.label :password %><br />
11    <%= f.password_field :password, autocomplete: "off" %>
12  </div>
13
14  <% if devise_mapping.rememberable? -%>
15    <div class="field">
16      <%= f.check_box :remember_me %>
17      <%= f.label :remember_me %>
18    </div>
19  <% end -%>
20
21  <div class="actions">
22    <%= f.submit "Log in" %>
23  </div>
24<% end %>
25
26<%= render "devise/shared/links" %>
jsx

Now it is time to add this to our table and re-migrate:

1class DeviseCreateUsers < ActiveRecord::Migration[5.0]
2  def change
3    create_table :users do |t|
4      ## Database authenticatable
5      t.integer :studentid,  null: false
6      t.string :email,              null: false, default: ""
7      t.string :encrypted_password, null: false, default: ""
8
9      ## Recoverable
10      t.string   :reset_password_token
11      t.datetime :reset_password_sent_at
12
13      ## Rememberable
14      t.datetime :remember_created_at
15
16      ## Trackable
17      t.integer  :sign_in_count, default: 0, null: false
18      t.datetime :current_sign_in_at
19      t.datetime :last_sign_in_at
20      t.inet     :current_sign_in_ip
21      t.inet     :last_sign_in_ip
22
23
24
25      t.timestamps null: false
26    end
27
28    add_index :users, :email,                unique: true
29    add_index :users, :reset_password_token, unique: true
30    # add_index :users, :confirmation_token,   unique: true
31    # add_index :users, :unlock_token,         unique: true
32  end
33end
ruby

You can add in validations here as you like. I would recommend adding in a unique validation so multiple users can not have the same id or in other cases, username.

Now Migrate again: rake db:migrate

Great! We've customized sign-in options and enabled sign-in. Now, we'll set up a subscribed connection if a user has signed in via Devise.

First make sure that you have ActionCable by seeing that you have the folder app/channels. After that, define a subscription by updating the file app/channels/connection.rb.

1module ApplicationCable
2  class Connection < ActionCable::Connection::Base
3    identified_by :current_user
4
5    def connect
6      self.current_user = find_verified_user
7      logger.add_tags 'ActionCable', current_user.studentid
8    end
9
10    protected
11
12    def find_verified_user # this checks whether a user is authenticated with devise
13      if verified_user = env['warden'].user
14        verified_user
15      else
16        reject_unauthorized_connection
17      end
18    end
19  end
20end
ruby

This will allow you to check the current user on the page and verify that the user is signed in. If so, the user becomes a valid subscriber. Clearly Devise and ActionCable work well together when it comes to authentication and user tracking.

Thanks for Reading

Thanks for reading this tutorial. Hopefully you found my methods easy to follow.