Xây dựng 1 ứng dụng Rails dùng xác thực không password
Bài đăng này đã không được cập nhật trong 8 năm
Password-less Authentication là gì ?
Password-less Authentication (PLA) là một kiểu xác thực không càn đến password. Nghĩa là chúng ta loại bỏ password ở cả bước đăng ký và đăng nhập.
Khi ta đăng ký, 1 email sẽ đc gửi đến địa chỉ email đó để t xác thực tài khoản. Còn khi đăng nhập thì ta cũng nhận được 1 email kèm theo 1 token để xác thực xem đúng tài khoản mà ta đã đăng ký bằng địa chỉ email đó hay không.
Ta sẽ xây dựng ứng dụng rails để thực hiện việc xác thực không password
rails new passwordless
Tạo model
Ta sẽ tạo bảng user thông qua scaffold cho tiện, với các thông tin cơ bản
rails g scaffold user fullname username:uniq email:uniq login_token token_generated_at:datetime
và chạy migrate
rails db:create && rails db:migrate
Thực hiện validate email và username cho user
app/models/user.rb
validates :email, :username, uniqueness: true, presence: true
Ta sẽ viết 1 callback để format email và username mà user nhập
before_save :format_email_username
def format_email_username
self.email = self.email.delete(' ').downcase
self.username = self.username.delete(' ').downcase
end
Ta sẽ viết function để tìm record của user thông qua email hoặc user name
def self.find_user_by(value)
where(["username = :value OR email = :value", {value: value}]).first
end
Đăng ký user
Ta sẽ tạp 1 trang static để hiển thị message và link đăng ký cho cho user
rails g controller static home
với routes file
root 'static#home'
Trong app/views/layouts/application.html.erb
ta sẽ đặt 1 đoạn notice cho user trong thẻ body
<p id="notice"><%= notice %></p>
và tạo user controller
rails g controller users
tạo routes đăng ký
resources :users, only: [:create]
get '/register', to: 'users#register'
Ta sẽ add nhưng function sau đây trong app/controllers/users_controller.rb
def register
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
redirect_to root_path, notice: 'Welcome! We have sent you the link to login to our app'
else
render :register
end
end
private
def user_params
params.require(:user).permit(:fullname, :username, :email)
end
Và view để user nhập các thông tin app/views/users/register.html.erb
<h1>Register</h1>
<%= form_for(@user) do |f| %>
<% if @user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@user.errors.count, "error") %> prohibited this @user from being saved:</h2>
<ul>
<% @user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :fullname %>
<%= f.text_field :fullname %>
</div>
<div class="field">
<%= f.label :username %>
<%= f.text_field :username %>
</div>
<div class="field">
<%= f.label :email %>
<%= f.text_field :email %>
</div>
<div class="actions">
<%= f.submit 'Register' %>
</div>
<% end %>
Ta để ý thấy ở đây không có trường password. Và bây giờ ta sẽ tạo login link, để xác thực user mà ko cần password
Login Link
Thêm các function sau trong app/models/user.rb
def send_login_link
generate_login_token
template = 'login_link'
UserMailer.send(template).deliver_now
end
def generate_login_token
self.login_token = generate_token
self.token_generated_at = Time.now.utc
save!
end
def login_link
"http://localhost:3000/auth?token=#{self.login_token}"
end
def login_token_expired?
Time.now.utc > (self.token_generated_at + token_validity)
end
def expire_token!
self.login_token = nil
save!
end
private
def generate_token
SecureRandom.hex(10)
end
def token_validity
2.hours
end
Ta có function send_login_link
để gửi login link đến email của user. Trước khi lưu vào db thì ta đã mã hoá nó thông qua BCrypt.
Bây giờ ngay sau khi save user vào db ta sẽ gọi function này để gửi mail xác thực đenemail của user. Cụ thể là ta thêm trong user controller
if @user.save
@user.send_login_link
Bây giờ ta sẽ thưc hiện việc xử lý nhận login link mà ta đã gửi cho user
Session Controller
rails g controller session auth
update lại routes file
đổi get 'session/auth'
thành get '/auth/:user_id/:token', to: 'session#auth'
Và trong sesstions_controller ta thêm các hàm sau
def auth
token = params[:token].to_s
user_id = params[:user_id]
user = User.find_by(id: user_id)
if !user || !user.valid_token?
redirect_to root_path, notice: 'It seems your link is invalid. Try requesting for a new login link'
elsif user.login_token_expired?
redirect_to root_path, notice: 'Your login link has been expired. Try requesting for a new login link.'
else
sign_in_user(user)
redirect_to root_path, notice: 'You have been signed in!'
end
end
Ở đây, ta check token có valid hay không, nếu không valid ta sẽ cho redirect đến home page kèm thông báo lỗi. Ta đã dùng function sign_in_user
def sign_in_user(user)
user.expire_token!
session[:email] = user.email
end
def current_user
User.find_by(email: session[:email])
end
Ở đây khi gọi sign_in_user ta đã để expire token và lưu email của user vào session.
Login
Bước cuối cùng ta sẽ thực hiện login user. Ta sẽ update routes file
resources :session, only: [:new, :create]
và sessions_controller
def new
end
def create
value = params[:value].to_s
user = User.find_user_by(value)
if !user
redirect_to new_session_path, notice: "Uh oh! We couldn't find the username / email. Please try again."
else
user.send_login_link
redirect_to root_path, notice: 'We have sent you the link to login to our app'
end
end
Sau khi nhập user name hoặc email thì ta sẽ thực hiện gửi login link đến email của úuer. Cuối cùng ta tạo form cho user nhập
<%= form_tag "/session" do %>
<label> Email / Username </label>
<%= text_field_tag "value" %>
<%= submit_tag "Login" %>
<% end %>
Trên đay ta đã thực hiện 1 ứng dụng đơn giản với chức năng xác thực không có password. Cách xác thực này đang ngày càng trở nên phổ biến và khá an toàn. Hi vọng có thể áp dụng vào các ứng dụng sau này của bạn.
All rights reserved