如何用Rodauth进行Rails认证

141 阅读2分钟

用Rodauth进行Rails认证

在本教程中,我们将展示如何使用Rodauth认证框架在Rails应用中添加全功能认证和账户管理功能。与Devise、Sorcery、Clearance和Authlogic等主流替代方案相比,Rodauth有很多优势。

我们将使用PostgresSQL、Hotwire、Bootstrap、主页、导航条、flash消息和帖子脚手架设置来处理一个新的Rails应用。

$ rails new blog --database=postgresql --css=bootstrap
$ cd blog
$ rails db:create
$ rails generate controller home index
$ rails generate scaffold post title:string body:text
$ rails db:migrate
# config/routes.rb
Rails.application.routes.draw do
  root to: "home#index"
  resources :posts
end
<!-- app/views/layouts/application.html.erb -->
<!-- ... -->
  <body>
    <%= render "navbar" %>

    <div class="container">
      <%= render "flash" %>
      <%= yield %>
    </div>
  </body>
<!-- ... -->
<!-- app/views/application/_flash.html.erb -->
<% if notice %>
  <div class="alert alert-success"><%= notice %></div>
<% end %>
<% if alert %>
  <div class="alert alert-danger"><%= alert %></div>
<% end %>
<!-- app/views/application/_navbar.html.erb -->
<nav class="navbar navbar-expand-sm navbar-light bg-light border-bottom mb-4">
  <div class="container">
    <%= link_to "Rails App", root_path, class: "navbar-brand" %>

    <div class="navbar-collapse">
      <ul class="navbar-nav">
        <li class="nav-item">
          <%= link_to "Posts", posts_path, class: "nav-link #{"active" if request.path.start_with?("/posts")}" %>
        </li>
      </ul>
    </div>
  </div>
</nav>

安装Rodauth

让我们先把rodauth-railsgem添加到我们的Gemfile中。

$ bundle add rodauth-rails

接下来,我们将运行由rodauth-rails提供的rodauth:install 生成器。

$ rails generate rodauth:install

# create  db/migrate/20200820215819_create_rodauth.rb
# create  config/initializers/rodauth.rb
# create  config/initializers/sequel.rb
# create  app/misc/rodauth_app.rb
# create  app/misc/rodauth_main.rb
# create  app/controllers/rodauth_controller.rb
# create  app/models/account.rb
# create  app/mailers/rodauth_mailer.rb

这将创建Rodauth应用程序和一些默认的Rodauth配置,配置Rodauth用于数据库交互的Sequel重用Active Record的数据库连接,并生成一个迁移,为加载的Rodauth功能创建表。让我们运行迁移。

$ rails db:migrate

# == CreateRodauth: migrating ==========================
# -- create_table(:accounts)
# -- create_table(:account_password_hashes)
# -- create_table(:account_password_reset_keys)
# -- create_table(:account_verification_keys)
# -- create_table(:account_login_change_keys)
# -- create_table(:account_remember_keys)
# == CreateRodauth: migrated ===========================

我们还需要为Rodauth设置Action Mailer的默认URL选项,以便能够在RodauthMailer 中生成电子邮件链接。

# config/environments/development.rb
Rails.application.configure do
  # ...
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
end

重启Rails服务器后,我们应该能够打开/create-account 页面,看到Rodauth的默认注册表。

Rodauth create account page

添加认证链接

由rodauth-rails生成的Rodauth配置为认证和账户管理提供了几条路线。

$ rails rodauth:routes

# /login                   rodauth.login_path
# /create-account          rodauth.create_account_path
# /verify-account-resend   rodauth.verify_account_resend_path
# /verify-account          rodauth.verify_account_path
# /logout                  rodauth.logout_path
# /remember                rodauth.remember_path
# /reset-password-request  rodauth.reset_password_request_path
# /reset-password          rodauth.reset_password_path
# /change-password         rodauth.change_password_path
# /change-login            rodauth.change_login_path
# /verify-login-change     rodauth.verify_login_change_path
# /close-account           rodauth.close_account_path

让我们利用这些信息,在我们的导航栏中添加一些主要的认证链接。

<!-- app/views/application/_navbar.html.erb -->
<!-- ... --->
<% if rodauth.logged_in? %>
  <div class="dropdown">
    <button class="btn btn-info dropdown-toggle" data-bs-toggle="dropdown" type="button">
      <%= current_account.email %>
    </button>
    <div class="dropdown-menu dropdown-menu-end">
      <%= link_to "Change password", rodauth.change_password_path, class: "dropdown-item" %>
      <%= link_to "Change email", rodauth.change_login_path, class: "dropdown-item" %>
      <div class="dropdown-divider"></div>
      <%= link_to "Close account", rodauth.close_account_path, class: "dropdown-item text-danger" %>
      <%= link_to "Sign out", rodauth.logout_path, data: { turbo_method: :post }, class: "dropdown-item" %>
    </div>
  </div>
<% else %>
  <div>
    <%= link_to "Sign in", rodauth.login_path, class: "btn btn-outline-primary" %>
    <%= link_to "Sign up", rodauth.create_account_path, class: "btn btn-success" %>
  </div>
<% end %>
<!-- ... --->

这里我们使用rodauth-rails提供的#current_account 辅助方法,该方法返回当前登录的账户。

现在我们的应用程序将在用户没有登录的时候显示登录和注册链接。

Rodauth login and registration links

在登录的时候,用户会看到一些基本的账户管理链接。

Rodauth account management links

要求认证

现在我们有了有效的认证,我们很可能想要求用户对我们应用程序的某些部分进行认证。在我们的案例中,我们想对帖子控制器进行认证。

我们可以在控制器中添加一个before_action 回调,但Rodauth允许我们在Rodauth应用的路由块中这样做,它在每个Rails路由之前被调用。这样,我们就可以把我们的认证逻辑控制在一个地方。

# app/misc/rodauth_app.rb
class RodauthApp < Rodauth::Rails::App
  # ...
  route do |r|
    # ...
    if r.path.start_with?("/posts")
      rodauth.require_authentication
    end
  end
end

现在,如果用户没有登录,访问/posts 页面将被重定向到/login 页面。

Rodauth login required

我们还想把帖子关联到accounts 表。

$ rails generate migration add_account_id_to_posts account:references
$ rails db:migrate
# app/models/account.rb
class Account < ApplicationRecord
  # ...
  has_many :posts
end

并将它们的范围扩大到post控制器中的当前账户。

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  # ...
  def index
    @posts = current_account.posts.all
  end
  # ...
  def create
    @post = current_account.posts.build(post_params)
    # ...
  end
  # ...
  private
    def set_post
      @post = current_account.posts.find(params[:id])
    end
    # ...
end

添加新的字段

为了显示用户的电子邮件地址以外的东西,让我们要求用户在注册时输入他们的名字。这也将给我们一个机会来看看Rodauth是如何被配置的。

由于我们需要编辑注册表,让我们先把Rodauth的HTML模板复制到我们的Rails应用程序中。

$ rails generate rodauth:views

# create  app/views/rodauth/_login_form.html.erb
# create  app/views/rodauth/_login_form_footer.html.erb
# create  app/views/rodauth/_login_form_header.html.erb
# create  app/views/rodauth/login.html.erb
# create  app/views/rodauth/multi_phase_login.html.erb
# create  app/views/rodauth/logout.html.erb
# create  app/views/rodauth/create_account.html.erb
# create  app/views/rodauth/verify_account_resend.html.erb
# create  app/views/rodauth/verify_account.html.erb
# create  app/views/rodauth/reset_password_request.html.erb
# create  app/views/rodauth/reset_password.html.erb
# create  app/views/rodauth/change_password.html.erb
# create  app/views/rodauth/change_login.html.erb
# create  app/views/rodauth/close_account.html.erb

我们现在可以打开create_account.erb 模板,添加一个新的name 字段。

<!-- app/views/rodauth/create_account.erb -->
<%= form_with url: rodauth.create_account_path, method: :post, data: { turbo: false } do |form| %>
  <!-- new "name" field -->
  <div class="mb-3">
    <%= form.label :name, "Name", class: "form-label" %>
    <%= form.text_field :name, value: params[:name], required: true, class: "form-control #{"is-invalid" if rodauth.field_error("name")}", aria: ({ invalid: true, describedby: "login_error_message" } if rodauth.field_error("name")) %>
    <%= content_tag(:span, rodauth.field_error("name"), class: "invalid-feedback", id: "login_error_message") if rodauth.field_error("name") %>
  </div>
  <!-- ... -->
<% end %>

由于用户的名字不会被用于认证,让我们把它存储在一个新的profiles 表里,并把profiles 表与accounts 表联系起来。

$ rails generate model Profile account:references name:string
$ rails db:migrate
# app/models/account.rb
class Account < ApplicationRecord
  # ...
  has_one :profile
end

我们现在需要我们的Rodauth应用程序实际处理新的name 参数。我们将验证它是否被填入,并在账户创建后创建相关的配置文件记录。

# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    # ...
    before_create_account do
      # Validate presence of the name field
      throw_error_status(422, "name", "must be present") unless param_or_nil("name")
    end
    after_create_account do
      # Create the associated profile record with name
      Profile.create!(account_id: account_id, name: param("name"))
    end
    after_close_account do
      # Delete the associated profile record
      Profile.find_by!(account_id: account_id).destroy
    end
    # ...
  end
end

现在我们可以更新我们的导航条,使用用户的名字而不是他们的电子邮件地址。

  <button class="btn btn-info dropdown-toggle" data-bs-toggle="dropdown" type="button">
-   <%= current_account.email %>
+   <%= current_account.profile.name %>
  </button>

Displayed new account name

结束语

在本教程中,我们已经使用Rodauth认证框架逐步建立了一个完整的认证和账户管理流程。它支持登录和注销、带有电子邮件验证和宽限期的账户创建、密码变更和密码重置、带有电子邮件验证的电子邮件变更,以及关闭账户功能。我们已经看到了如何添加认证链接,要求对某些路线进行认证,以及在注册表格中添加新的字段。