Bear Su's Blog

Rails 使用者驗證:Devise Gem Getting Start

Devise gem 是在 Rails 社群中受到廣泛使用於使用者驗證的 gem。我們可以使用 Devise 在 Rails 專案快速實現最基礎的使用者驗證功能,例如註冊、登入、以及忘記密碼等等。

簡介


雖然 Devise 可以協助我們快速實作使用者驗證功能,但有趣的是在 Repo 的 README Starting with Rails? 章節,寫著並不推薦 Rails 初學者使用。這是因為 Devise 是基於 Rails 框架設計,如果完全不了解 Rails,在開發上就很容易迷路。建議從了解 Rails 的架構開始,並嘗試自己建立一個使用者驗證的功能來了解相關的運作原理,我推薦這一篇文章:Building a simple authentication in Rails 7 from scratch

本文我們建立一個新的 Rails 專案來體驗一下如何在 Rails 中加入 Devise 來提供使用者驗證功能。

環境


本文使用的環境:

  • macOS
  • Ruby 3.2.2
  • Rails 7.1.2

安裝


建立測試 Rails 專案

建立 Rails 專案

rails new demo-devise
cd demo-devise

使用 Scaffold 建立 Post Controller 與 Model

bin/rails g scaffold posts title:string content:text

更新 DB Schema

bin/rails db:migrate

更新路由表 config/routes.rb

Rails.application.routes.draw do
  resources :posts
  root "posts#index"

  get "up" => "rails/health#show", as: :rails_health_check
end

啟動 Rails Server 來確認一下

bin/rails server

用瀏覽器開啟網址 http://localhost:3000/

Post Index

由於沒有使用者驗證,我們可以任意地新增修改資料。

安裝 GEM

直接執行指令安裝

bundle add devise

或是先修改 Gemfile 再直接執行指令安裝

gem "devise"
bundle install

設定


產生設定檔

bin/rails g devise:install

可以從輸出結果看到新增了兩個檔案,以及後續的設定提示:

Devise Install Result

第一個提示是設定測試環境的 default url,這個是為了能夠正確的產生信件內容。Devise 提供的使用者驗證功能中,像是忘記密碼就會寄送重設密碼信給使用者。為了能夠在本機測試環境正常運行,我們需要進行設定。另外為了方便測試,我們也一同安裝 letter_opener gem

bundle add letter_opener --group "development"

更新 config/environments/development.rb,加上:

config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

config.action_mailer.delivery_method = :letter_opener
config.action_mailer.perform_deliveries = true

更新驗證 Model 與路由表

我們透過 Generator 來產生使用者驗證所需要的 DB Schema,通常是使用 User Model,但也可以用其他名稱作為使用者驗證的 Model。

bin/rails g devise User
Devise Generate Result

我們來查看一下指令產生的 Migration File:

# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[7.1]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      # t.integer  :sign_in_count, default: 0, null: false
      # t.datetime :current_sign_in_at
      # t.datetime :last_sign_in_at
      # t.string   :current_sign_in_ip
      # t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at


      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

可以發現幾件事:

  1. Devise 新增了 users 資料表。
  2. users 資料表預設有兩個用於驗證身份的必要欄位:emailencrypted_password
  3. 預設啟用忘記密碼與記住登入的功能。
  4. 預設停用追蹤登入資訊、驗證信箱、鎖定帳號的功能,我們可以移除註解或是之後再新增啟用。

我們查看一下 User Model:

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
end

我們查看一下 config/routes.rb,已被加上了 Devise 的路由:

Rails.application.routes.draw do
  devise_for :users
  resources :posts
  root "posts#index"

  get "up" => "rails/health#show", as: :rails_health_check
end

執行指令更新 DB Schema

bin/rails db:migrate

加入驗證

我們做好了驗證 Model 的相關設定,接下來就要用 Devise 來保護我們的頁面功能。

接下來我們將設定 Post 頁面除了 indexshow 方法,其他頁面我們都要求通過身份驗證才能存取:

class PostsController < ApplicationController
  before_action :authenticate_user!, except: %i[index show]
  before_action :set_post, only: %i[ show edit update destroy ]

...

用瀏覽器開啟網址 http://localhost:3000/

首頁可以正常存取:

Post Index

點擊 New Post 連結會轉導到登入頁面:

Devise Login Page

我們點擊 Sign Up 註冊一個新使用者:

Devise Register Page

成功後會自動轉導到 New Post 頁面,並可以成功新增資料:

New Post Page
Create Post Successful

為了能夠確認登入狀態與登出,更新 app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>DemoDevise</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_importmap_tags %>
  </head>

  <body>
    <div>
      <% if user_signed_in? %>
        <span><%= current_user.email %></span>
        <%= link_to "Sign Out", destroy_user_session_path, data: {turbo_method: :delete }%>
      <% else %>
        <%= link_to "Sign In", new_user_session_path %>
      <% end %>
    </div>

    <%= yield %>
  </body>
</html>

其中 user_signed_in?current_user 都是由 Devise 所提供的方法

我們重新整理頁面:

Show login status

現在可以看到登入的使用者信箱和登出連結。

測試信件

我們可以使用忘記密碼功能,讓 Devise 寄送信件。

我們回到登入頁面,點擊 Forgot your password?:

Click Forgot your password?

輸入註冊的使用者信箱後點擊 Send me reset password instructions:

Click Send me reset password instructions

因為我們在開發環境使用了 letter_opener gem,當 Rails 寄送信件時,瀏覽器會彈跳出預覽信件的視窗,我們點擊信件內容中的連結就可以為使用者重新設定密碼:

Check reset password mail
Reset password

總結


透過本文的步驟,我們一步步使用 Devise 在 Rails 專案快速實現最基礎的使用者驗證功能:

  • 保護頁面功能,自動轉導到登入頁面
  • 登入
  • 註冊
  • 修改密碼

我們可以發現後面我們在 Router 上用一行就設定好相關的路由,在 Controller 上用一行就設定好保護了哪些頁面,在頁面中我們使用 Devise 提供的 Helper 方法取得登入資訊。但現在開發下,我們常常會需要進行客製化。Devise 當然也提供了客製化的選項,除了透過修改設定檔的值之外,我們也能透過 Devise 的 Generator 來產生檔案,去覆寫 Devise 原本提供的功能行為與頁面樣式。

另外最近有個有趣的新聞是 Rails 8 預計加入使用者驗證功能的 Generator:Add basic authentication generator,有興趣的話可以加入討論提供建議。

參考資料



如果覺得這篇文章對您有所幫助,歡迎贊助我一杯咖啡 ☕️

祝您有美好的一天 ❤️