[RAILS] Story of implementing login function using gem sorcery

What did you do

I implemented the login function using a gem called sorcery. It was my first time to use a gem, and there were few documents that could be seen in Japanese, so I would like to make a runbook and manual for myself.

The execution environment is as follows.

What kind of gem?

It is a gem with the minimum logic for authentication, and aims to increase or extend methods as needed by the user.

▼ Click here for official documents Sorcery/sorcery

Introduction method

The steps in this article generally follow the Official Tutorial (https://github.com/Sorcery/sorcery/wiki/Simple-Password-Authentication). It describes from the introduction of Gem to the implementation of login / logout function.

The procedure is as follows. First, write the following in the Gemfile ...

Gemfile


gem 'sorcery'

bundle install.

Then, if you execute the following command

$ rails generate sorcery:install

The following files will be generated.

create  app/models/user.rb
create  db/migrate/XXXXXXXXX_sorcery_core.rb

Also, here is the content of the generated migration file.

XXXXXXXXX_sorcery_core.rb


class SorceryCore < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :email,            null: false
      t.string :crypted_password
      t.string :salt

      t.timestamps                null: false
    end
    add_index :users, :email, unique: true
  end
end

You still know the crypted_password, but the salt column is a mystery, isn't it? I found this article on the WEB, the following is the Google Translate.

Standard encryption uses "salt" to make password hashes more secure. Sorcery does this by combining a random string at the end of the password and storing that string in the salt field.

So that's it. .. .. Do rails db: migrate and the installation is completed successfully.

Implementation (1): User creation

View The data in the crypted_password and salt columns is not written in the view so that the user cannot directly change or display it. Instead, the view uses password and password_confirmation.

ruby:app/views/users/new.html.slim


= form_with model: @user, local: true do |f|
  = f.label :email
  = f.text_field :email
  = f.label :password
  = f.password_field :password
  = f.label :password_confirmation
  = f.password_field :password_confirmation
  = f.submit "Registration"

Controller

Also, the controller strong_paramater will allow you to receive password and password_confirmation respectively.

app/controllers/users_controller.rb


class UsersController < ApplicationController
  # ...
  private

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

Model The model file is described as follows.

app/models/user.rb


class User < ActiveRecord::Base
  authenticates_with_sorcery!

  validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] }
  validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }
  validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] }

  validates :email, uniqueness: true
end

First, use authenticates_with_sorcery! to give the Uder model the ability to authenticate with sorcery. For columns that are not in the model, such as password and password_confirmation

validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }

This description makes sorcery aware that it is the contents of the encrypted_password column.

Digression

As an aside, the new_redord? Method looks like Active Record's method to determine if an instance is newly created ...: relaxed: (I didn't know & & useful! )

Also, the following if:-> is Symbols, Lambdas and Proc parts of conditional validation. I'm not good at it, so I have to do my best to review it. .. ..

Try on the console

Once you've done that, try rails c to make sure the user can create it successfully.

#console
> User.create!(email: "[email protected]", password: "test12345", password_confirmation: "test12345")

> User.last
=> #<User:0x00007fb37b8b4d28
 id: 1,
 email: "[email protected]",
 crypted_password:
  "$2a$10$dOdyPmn65bJ4LKjG5vdL2eU2Hypzpqz5fEgp4LTF8ai0lJtBKL.Q6",
 salt: "QapQbW9crUf1QixW2BD7",
 username: "testuser",
 created_at: Tue, 22 Dec 2020 14:48:37 UTC +00:00,
 updated_at: Tue, 22 Dec 2020 14:48:37 UTC +00:00>

You have successfully created a user with crypted_password and salt: relaxed:

Postscript

By the way, I noticed that I didn't describe the methods of the Users controller, so I will give you the whole picture of users_controller.

controllers/users_controller.rb


class UsersController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to login_path
      flash[:notice] = 'You have successfully created a user'
    else
      flash.now[:alert] = 'Failed to create user'
      render :new
    end
  end

  private

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

Now you can also create users from the screen ^ ^

Implementation (2): Creation of login / logout function

Now, I would like to implement the login / logout function. The methods provided by sorcery are described in Official part here.

According to it, the login method can take three arguments: email, password, and remenber_me (default value is false).

login(email, password, remember_me = false)

This time, we are not using the remenber_me function, so we will implement the login function with email and password. First, generate a controller from the terminal ...

$ rails g controller UserSessions new create destroy

I will describe the controller.

app/controllers/user_sessions_controller.rb


class UserSessionsController < ApplicationController
  def new
  end

  def create
    @user = login(params[:email], params[:password])

    if @user
      redirect_back_or_to(new_user_path, notice: 'Login was successful')
    else
      flash.now[:alert] = 'I failed to login'
      render :new
    end
  end

  def destroy
    logout
    redirect_to(login_path, notice: 'logged out')
  end
end

redirect_back_or_to is a function that realizes friendly forwarding with sorcery, and according to this description, it takes(url, flash_hash = {})as an argument. ..

Variable name = {} is the way to write when receiving the entire hash as an argument. It was written on p.165 of Cherry Book.

The logout method doesn't need any explanation.

Router The routing is defined as follows.

config/routes.rb


Rails.application.routes.draw do

  get 'login', to: 'user_sessions#new'  #Postscript
  post 'login', to: 'user_sessions#create'  #Postscript
  delete 'logout', to: 'user_sessions#destroy'  #Postscript
  root 'users#new'

  resources :users, only: %i(new create)
end

I don't think there is any particular explanation here either.

View

Finally, View is defined as follows: Again, I didn't have to worry about it.

ruby:views/layouts/application.html.slim


  body
    = render 'shared/header'
    = yield

The logged_in? Below is a ** sorcery method that can be used in views **, according to the Official Documentation (https://github.com/Sorcery/sorcery#core).

ruby:views/shared/_header.html.slim


h1 AppName
nav
  - if logged_in?
    = link_to 'Log out', logout_path, method: :delete
  - else
    = link_to 'Login', login_path

Ruby:views/user_sessions/new.html.slim


= form_with url: login_path, method: :post do |f|
  = f.label :email
  = f.text_field :email
  = f.label :password
  = f.password_field :password
  = f.submit 'Login'

Complete!

With the above, the login / logout method has been implemented. I will improve the appearance etc. from now on, but it was easier than I imagined ^ ^ Image from Gyazo

Image from Gyazo

As for the impression, since I made it for the first time, I felt that it was a hassle to make a simple login form using the default functions of Rails without using Gem etc.

I didn't feel that the implementation was quirky like in devise, and there seems to be SNS authentication etc. in the method list that can be realized with sorcery, so I would like to continue trying various things ^ ^

Recommended Posts

Story of implementing login function using gem sorcery
Story of implementing update function in form object
[Note] Summary of rails login function using devise ①
Addition of guest login function
[Rails] Implementation of tagging function using intermediate table (without Gem)
Implementation of user authentication function using devise (2)
Rails Addition of easy and easy login function
Implementation of user authentication function using devise (1)
Implementation of user authentication function using devise (3)
Login function
Create a login function using Swift's Optional
[Rails] Implementation of search function using gem's ransack
Implementation of Ruby on Rails login function (Session)
[Rails 6] Implementation of inquiry function using Action Mailer
[Rails] Implementation of image enlargement function using lightbox2
[Rails] Implementation of batch processing using whenever (gem)
Story of test automation using Appium [Android / java]
Ruby on Rails <2021> Implementation of simple login function (form_with)
I tried using the Server Push function of Servlet 4.0
[Rails] Test of star evaluation function using Raty [Rspec]
Implementation of Ruby on Rails login function (devise edition)
[Rails] Implementation of multi-layer category function using ancestry "Preparation"
[Rails] Implementation of multi-layer category function using ancestry "seed"
The story of making Dr. Oakid using LINE BOT
Rails tutorial (6th edition) Background operation of login function
Create a login authentication screen using the session function