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.
Rails 5.2.3
Ruby 2.6.4
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
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.
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.
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. .. ..
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:
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 ^ ^
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'
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 ^ ^
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