如何使用Rodauth的Rails认证

268 阅读4分钟

0.动机

在[BootrAils],直到最近,我们才对任何新的Rails应用程序中的体面的默认认证感到不安--直到Rodauth出现在人们的视线之下。

在Ruby-on-Rails世界中没有 "Active Auth",这意味着如果你想在你的应用程序中添加身份验证,你必须依靠一个gem--或者自己建立它。

对于那些已经了解这个领域的人来说,这是一个在互联网上无休止的辩论。Devise是最常用的宝石。然而,长期用户对它的评价总是 "一般般":Devise对角落里的情况并不那么好(处理JWT认证是抱怨之一,还有其他许多抱怨)。Clearance、Sorcery是众所周知的替代品,但它们也与Rails本身紧密耦合,必要时不太容易调整。

1.进入Rodauth

Rodauth消除了上述的大部分痛苦。Rodauth最初不与Rails绑定(它是一个Ruby库)。它自带以下[功能]。

  • 登录
  • 注销
  • 更改密码
  • 更改登录
  • 重置密码
  • 创建账户
  • 关闭账户
  • 验证账户
  • 确认密码
  • 记住 (通过令牌自动登录)
  • 锁定(暴力保护)
  • 审计记录
  • 电子邮件认证(通过电子邮件链接进行无密码登录)
  • WebAuthn (通过WebAuthn的多因素认证)
  • WebAuthn登录 (通过WebAuthn无密码登录)
  • WebAuthn验证帐户(无密码的WebAuthn设置)
  • OTP (通过TOTP的多因素认证)
  • 恢复代码(通过备份代码进行多因素认证)
  • 短信代码 (通过短信进行多因素认证)
  • 验证登录变更 (在变更登录前验证新的登录)
  • 验证账户宽限期 (登录前不需要验证)
  • 密码宽限期(如果是最近输入的密码,则不要求输入)。
  • 密码复杂性 (更复杂的检查)
  • 密码胡椒粉
  • 不允许重复使用密码
  • 不允许普通密码
  • 密码过期
  • 帐户过期
  • 会话过期
  • 活动会话(防止注销后重复使用会话,允许注销所有会话)
  • 单一会话 (每个账户只有一个活动会话)
  • JSON (所有其他功能都支持JSON API)
  • JWT (支持所有其他功能的JSON网络令牌)
  • JWT刷新 (访问和刷新令牌)
  • JWT CORS (跨源资源共享)
  • 更新密码散列(当散列成本改变时)
  • Argon2
  • HTTP基本认证
  • 更改密码通知
  • 内部请求
  • 路径类方法

对于一个开始来说还不错!对于一个标准的企业来说,你需要其他东西的可能性几乎为零。

没有必要说我们不会涵盖每一个功能,但知道我们不会错过任何东西总是很好的!

2.试试吧,从头开始

首先确保你的电脑上已经安装了所有的经典软件。

$> ruby -v  
ruby 3.0.0p0 // you need at least version 3 here  
$> bundle -v  
Bundler version 2.2.11  
$> npm -v  
8.3.0 // you need at least version 7.1 here  
$> yarn -v  
1.22.10
$> psql --version  
psql (PostgreSQL) 13.1 // let's use a production-ready database locally  

任何上面的版本都可以使用

然后从头开始安装一个新的rails应用程序。

  mkdir myapp && cd myapp  
  echo "source 'https://rubygems.org'" > Gemfile  
  echo "gem 'rails', '7.0.0'" >> Gemfile  
  bundle install  
  bundle exec rails new . --force --css=bootstrap -d=postgresql  
  bundle update

Bootstrap会让我们有一个更漂亮的演示。或者至少更有可读性 :)

在myapp文件夹中,继续执行以下终端命令。

  # Create a default controller
  echo "class HomeController < ApplicationController" > app/controllers/home_controller.rb
  echo "end" >> app/controllers/home_controller.rb

  # Create another controller (the one that should not be reached without proper authentication)
  echo "class OtherController < ApplicationController" > app/controllers/other_controller.rb
  echo "end" >> app/controllers/other_controller.rb

  # Create routes
  echo "Rails.application.routes.draw do" > config/routes.rb
  echo '  get "home/index"' >> config/routes.rb
  echo '  get "other/index"' >> config/routes.rb
  echo '  root to: "home#index"' >> config/routes.rb
  echo 'end' >> config/routes.rb

  # Create a default view
  mkdir app/views/home
  echo '<h1>This is home</h1>' > app/views/home/index.html.erb
  echo '<div class="lead my-3"><%= link_to "go to other page", other_index_path %></div>' >> app/views/home/index.html.erb

  # Create another view (will be also protected by authentication)
  mkdir app/views/other
  echo '<h1>This is another page</h1>' > app/views/other/index.html.erb
  echo '<div class="lead my-3"><%= link_to "go to home page", root_path %></div>' >> app/views/other/index.html.erb
  
  
  # Create database and schema.rb
  bin/rails db:create
  bin/rails db:migrate
  

很好!我们现在有了一个很好的默认的Rails 7应用程序,有一个主页,还有一个 "其他 "页面,它应该被保护起来,防止未经认证的访问。

通过运行以下命令偷看一下当前的应用程序

./bin/dev

并打开 http://localhost:3000

localhost

本地主机

从一个页面导航到另一个页面。到目前为止,还没有什么不可思议的东西,但至少我们已经准备好去尝试一个好的认证宝石了 !

3.安装rodauth-rails

现在打开你的Gemfile,添加

gem "rodauth-rails"

然后

$/myapp> bundle install

让我们看看它是什么。

$/myapp> bundle info rodauth-rails
  * rodauth-rails (0.18.1)
    Summary: Provides Rails integration for Rodauth.
    Homepage: https://github.com/janko/rodauth-rails
    Path: /Users/shino/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rodauth-rails-0.18.1

很好!为下一关做好准备 :)

4.在你的应用程序中安装rodauth

创业板现在可用了,但还没有在你的Rails应用程序中运行rodauth的必要文件和文件夹。

让我们开始行动吧。

$/myapp> bin/rails generate rodauth:install
      create  db/migrate/20211224143551_create_rodauth.rb
      create  config/initializers/rodauth.rb
      create  config/initializers/sequel.rb
      create  app/lib/rodauth_app.rb
      create  app/controllers/rodauth_controller.rb
      create  app/models/account.rb
      create  app/mailers/rodauth_mailer.rb
      create  app/views/rodauth_mailer/email_auth.text.erb
      create  app/views/rodauth_mailer/password_changed.text.erb
      create  app/views/rodauth_mailer/reset_password.text.erb
      create  app/views/rodauth_mailer/unlock_account.text.erb
      create  app/views/rodauth_mailer/verify_account.text.erb
      create  app/views/rodauth_mailer/verify_login_change.text.erb

在你最喜欢的IDE中偷看一下每个文件。

然后输入 。

$/myapp> bin/rails db:migrate
== 20211224143551 CreateRodauth: migrating ====================================
-- enable_extension("citext")
   -> 0.1350s
-- create_table(:accounts)
   -> 0.0084s
-- create_table(:account_password_hashes)
   -> 0.0066s
-- create_table(:account_password_reset_keys)
   -> 0.0081s
-- create_table(:account_verification_keys)
   -> 0.0217s
-- create_table(:account_login_change_keys)
   -> 0.0080s
-- create_table(:account_remember_keys)
   -> 0.0050s
== 20211224143551 CreateRodauth: migrated (0.1933s) ===========================

现在schema.rb看起来像这样。

ActiveRecord::Schema.define(version: 2021_12_24_143551) do

  enable_extension "citext"
  enable_extension "plpgsql"

  create_table "account_login_change_keys", force: :cascade do |t|
    t.string "key", null: false
    t.string "login", null: false
    t.datetime "deadline", precision: 6, null: false
  end

  create_table "account_password_hashes", force: :cascade do |t|
    t.string "password_hash", null: false
  end

  create_table "account_password_reset_keys", force: :cascade do |t|
    t.string "key", null: false
    t.datetime "deadline", precision: 6, null: false
    t.datetime "email_last_sent", precision: 6, default: -> { "CURRENT_TIMESTAMP" }, null: false
  end

  create_table "account_remember_keys", force: :cascade do |t|
    t.string "key", null: false
    t.datetime "deadline", precision: 6, null: false
  end

  create_table "account_verification_keys", force: :cascade do |t|
    t.string "key", null: false
    t.datetime "requested_at", precision: 6, default: -> { "CURRENT_TIMESTAMP" }, null: false
    t.datetime "email_last_sent", precision: 6, default: -> { "CURRENT_TIMESTAMP" }, null: false
  end

  create_table "accounts", force: :cascade do |t|
    t.citext "email", null: false
    t.string "status", default: "unverified", null: false
    t.index ["email"], name: "index_accounts_on_email", unique: true, where: "((status)::text = ANY ((ARRAY['unverified'::character varying, 'verified'::character varying])::text[]))"
  end

  add_foreign_key "account_login_change_keys", "accounts", column: "id"
  add_foreign_key "account_password_hashes", "accounts", column: "id"
  add_foreign_key "account_password_reset_keys", "accounts", column: "id"
  add_foreign_key "account_remember_keys", "accounts", column: "id"
  add_foreign_key "account_verification_keys", "accounts", column: "id"
end

4.查看可用的路由

Rodauth中间件将处理请求(而不是Rails应用),因此,路由不会显示在/rails/info/routes。

从文档中,这里有可用的端点。

Routes handled by RodauthApp:

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

5.创建视图和用户体验

你有一些模板已经可以免费使用,如果你想看看事情是如何运作的。对于一个教程来说,这是一个完美的起点,所以让我们输入 。

$/myapp> bin/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/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/logout.html.erb
      create  app/views/rodauth/remember.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/verify_login_change.html.erb
      create  app/views/rodauth/close_account.html.erb

6.修改主页

现在修改主页,然后你就可以玩你的应用程序了。

<h1>This is home</h1>

<div class="lead my-3"><%= link_to "go to other page", other_index_path %></div>

<% if rodauth.logged_in? %>
  <%= link_to "Sign out", rodauth.logout_path, method: :post %>
<% else %>
  <%= link_to "Sign in", rodauth.login_path %>
  <%= link_to "Sign up", rodauth.create_account_path %>
<% end %>

现在启动你的本地服务器,并尝试创建一个新账户,注销,然后登录,上述标记应该可以正常工作。

如果你想在本地尝试 "重置密码 "的功能,别忘了在下面的一行中加入config/environments/development.rb

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

7.保护另一个页面

记住我们的应用程序中有2个页面:"主页 "和 "其他"(你可以通过http://localhost:3000/other/index,到达其他页面)

修改config/routes.rb如下。

# inside config/routes.rb
Rails.application.routes.draw do
  get "home/index"
  constraints Rodauth::Rails.authenticated do
    get "other/index"
  end
  root to: "home#index"
end

重新启动你的本地Web服务器。如果在主页上,你试图通过点击链接进入其他页面,会发生什么?

8.文档,信用

非常感谢@janko和@jeremyevans所做的不可思议的工作,以及对GitHub上的问题和PR的友好回答。