Xây dựng 1 ứng dụng Rails dùng xác thực không password
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 ...
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.