enjoy Railsway!! 第5回 Rails7.1: 独自の認証機能構築の改善

今月の半ば、2023年9月13日に Rails 7.1 Beta1 がリリースされました。

また、9月27日にはrc1もリリースされており、Rails 7.1の正式リリースも着々と近づいています。

公式ブログの記事では、次の新機能等が紹介されています。

  • アプリケーション作成時にDockerfileを生成
  • 独自の認証機能構築の改善
  • Active Recordへのクエリをより非同期に
  • Trilogy MySQLアダプタのサポート
  • Active Record: 複合主キーサポート
  • Active Job: 大量のジョブキューイングを効率化するメソッド
  • Autoloadingの拡張
  • JavaScriptランタイム Bun のサポート

皆さんは気になる話題はありますでしょうか。
今回はアプリケーション構築に直接影響がありそうなところ、「独自の認証機能構築の改善」を見ていきたいと思います。

※このコラムのプログラム等は以下の環境で動作確認しています
(2023/09/30 最終確認)
 ruby: ruby 3.2.2 [arm64-darwin22]
 rails: rails (7.1.0.rc1)

目次

独自の認証機能構築: has_secure_password

元々Railsには、ActiveModelのメソッドとしてhas_secure_passwordが用意されていました。

こちらを利用すると、安全な認証機能を簡便に構築することができます。
ここで使い方をおさらいして、新しい機能を見ていきましょう。

Gemfileでbcryptを設定

has_secure_passwordを利用するには、bcrypt Gemが必要です。
rails newした際に作成されるGemfileではコメントアウトされていますので、有効化してbundle installします。

Gemfile

# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
gem "bcrypt", "~> 3.1.7"

認証用のモデルにカラムを設定

パスワードはbcryptによって適切にダイジェスト化され保存されます。
保存先のカラムとして、認証用に使うモデルにpassword_digest(string)カラムを用意します。
今回は認証に使うモデルとしてUserというモデルを作成しました。

db/migration/create_users.rb

class CreateUsers < ActiveRecord::Migration[7.1]
  def change
    create_table :users do |t|
      t.string :email, null: false
      t.string :password_digest, null: false

      t.timestamps
      
      t.index :email, unique: true
    end
  end
end

モデルにメソッドを設定

認証に使うモデルにhas_secure_passwordを追記します。

app/models/user.rb

class User < ApplicationRecord
  has_secure_password
end

動作確認用のユーザー作成

今回はdb/seeds.rbを利用して確認用のユーザーを作成します。
Rails consoleで実行しても良いでしょう。

db/seeds.rb

User.create!(email: "alice@example.jp", password: "passw0rd!")

コマンド

bin/rails db:seed

認証機能として利用する(従来の方法)

Rails consoleを使って認証機能を確認してみます。
Userを検索した後、`authenticate`メソッドを利用してパスワードを検証します。

bin/rails c

# ユーザーを検索
irb> user = User.find_by(email: 'alice@example.jp')

# パスワードを確認
## 正しいときは、userインスタンスがそのまま返る
irb> user&.authenticate('passw0rd!')
=> #<User:... id: 1, email: "alice@example.jp", password_digest: "[FILTERED]", ...>
## 正しくないときは、falseが返る
irb> user&.authenticate('invalid')
=> false

実はこの方法には「タイミング攻撃」に対する脆弱性があります。
対象のユーザーが存在するかどうかで処理時間に有意な差が出てしまうことから、たとえ画面上にはそのようなメッセージを表示しないとしても、攻撃者は ユーザーが存在することを知ることができてしまいます。

新機能: タイミング攻撃を緩和するauthenticate_by

そこで今回新しく追加されたクラスメソッドが、authenticate_byです。

bin/rails c

# ユーザーを検索と同時にパスワードを検証
## 正しいときは、userインスタンスが返る
irb> user = User.authenticate_by(email: 'alice@example.jp', password: 'passw0rd!')
=> #<User:... id: 1, email: "alice@example.jp", password_digest: "[FILTERED]", ...>
## 正しくないときは、nilが返る
irb> user = User.authenticate_by(email: 'alice@example.jp', password: 'invalid')
=> nil
## ユーザーが存在しないときも、nilが返る
irb> user = User.authenticate_by(email: 'no-exist@example.jp', password: 'passw0rd!')
=> nil

このメソッドは、レコードの有無に関わらずパスワードダイジェストの計算をおこなうことで、タイミング攻撃に対する脆弱性を緩和する、とされています。
find_byのような検索を自分で書く必要もなくなるため、コードの見通しも少し良くなりそうです。

新機能: 自動的に正規化する normalizes

その他の追加メソッドも見ていきましょう。
指定したアトリビュートを自動的に正規化できる、normalizesメソッドが追加されました。
例えば「emailを扱う際は小文字に統一する」、といったことを標準機能として記述することができます。

app/models/user.rb

class User < ApplicationRecord
  has_secure_password

  normalizes :email, with: -> email { email.strip.downcase }
end

bin/rails c

irb> User.new(email: 'BOB@EXAMPLE.JP')
=> #<User:... id: nil, email: "bob@example.jp", ...>

# 認証で検索する際も自動的に正規化
irb> user = User.authenticate_by(email: 'ALICE@EXAMPLE.JP', password: 'passw0rd!')
=> #<User:... id: 1, email: "alice@example.jp", password_digest: "[FILTERED]", ...>

新機能: パスワード変更時に現在のパスワードを検証する

パスワードを更新する際に現在のパスワードを検証するようになりました。

bin/rails c

# 現在のパスワードが誤っていると更新できない
irb> user.update(password: 'new_passw0rd!', password_challenge: 'invalid')
=> false
# 現在のパスワードが正しいと更新できる
irb> user.update(password: 'new_passw0rd!', password_challenge: 'passw0rd!')
=> true

新機能: 目的に応じたトークンを生成する generate_token_for

目的に応じたトークンを生成して、トークンによってレコードを検索できるgenerate_token_forメソッドが追加されました。
有効期間や、トークンの有効性を検証するための処理を指定することもできます。

まとめ

Rails7.1で追加される予定の新機能のうち、「独自の認証機能構築の改善」を見てみました。
「Railsにおまかせ」でよりセキュアな認証機能を構築できるようになったことは、Railsコミュニティがきちんとセキュリティ問題に向き合っている証左と言えるでしょう。
認証機能はGemにお任せしてしまうことも多いと思いますが、Railsアプリケーション開発者としては、是非標準機能の進歩も抑えておきたいところです。

著者/文責: 泉 隼人
・Rails技術者認定試験運営委員会 テクニカルアドバイザー
・神奈川工科大学 情報工学科 客員研究員
・鹿児島県 喜界島出身。10歳の頃N88-BASICに触り、コンピューティングにのめり込む
・興味の赴くまま様々なプラットフォーム、言語を楽しみつつ10数年来に渡りRuby on Railsでの開発業務に従事する
・Webサイト https://9uelle.jp/

お知らせ

当委員会ではRails試験を全国300か所で一年中実施をしています。興味がある方は以下をご覧の上、是非受験ください。https://railsce.com/exam

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

目次