12/08/2018, 14:10

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.

Image

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.

0