Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

RailsのDeviseマスタリング入門:OmniAuth、API認証、Authtrailの活用方法

このシリーズの第1回では、サンプルアプリを通じてDeviseのモジュール、ヘルパー、ビュー、コントローラー、ルーティングについて学びました。

今回の第2回(最終回)では、より高度なDeviseの使い方として、OmniAuthによるソーシャルログインJWTを利用したAPI認証、そしてAuthtrailによるログイン履歴の追跡について詳しく解説していきます。

それでは早速始めましょう!

RubyでOmniAuthによる認証を実現する

現在、ほとんどすべてのWebアプリケーションで、TwitterやFacebookなどのSNS、Google、GitHubなど、多様な認証プロバイダーを使ったログイン機能が提供されています。

この便利なマルチプロバイダー認証の多くは、OmniAuthというライブラリによって支えられています。OmniAuthは、複数の外部プロバイダーとの連携を可能にする、柔軟かつ強力なRuby向け認証ライブラリです。

さまざまなOAuthプロバイダーに接続するためのシンプルで統一されたAPIを提供しており、特にユーザーがSNSアカウントを使って新規登録やログインできるようにしたい場合に役立ちます。OmniAuthを使えば、Railsアプリケーションに簡単にソーシャルログイン機能を追加できます。

さらに、OmniAuthをDevise gemと組み合わせると、ユーザー認証・認可の管理がさらに容易になります。Deviseの組み込み認証機能を活かしながら、外部プロバイダーでのログインオプションをOmniAuthに任せることができます。

OmniAuthとDeviseの導入手順

前述のとおり、OmniAuthは多数のサードパーティ認証プロバイダーとの統合を可能にします。本記事では例としてGitHubを使用します。

OmniAuth関連gemのインストール

アプリのGemfileに以下の行を追加してください。

OmniAuth 2.0以降のgemを使用している場合は、次のgemも追加する必要があります。

omniauth-rails_csrf_protection gemは、OAuthフローへのGETリクエストを無効化し、OAuthリクエストフェーズの前にRailsのCSRFトークン検証を挿入します。この2つの対策により、OAuth認証フローを狙ったクロスサイトフォージェリー攻撃(CSRF攻撃)を防ぐことができます。

その後、bundle installを実行してgemをインストールしましょう。

GitHubで新しいOAuthアプリを作成する

次に、GitHub上で新しいOAuthアプリを作成します。このアプリは認証権限を持つユーザーとして動作し、必要になった際には簡単に権限を取り消せるのが特徴です。

まずGitHubアカウントのプロフィールから設定ページへ移動し、左側のメニューにある「Developer settings」をクリックします。以下のような画面が表示されるので、ここで新しいOAuthアプリを作成できます。

RailsのDeviseマスタリング入門:OmniAuth、API認証、Authtrailの活用方法

「Register new application」をクリックすると、次のような画面が表示されます。

RailsのDeviseマスタリング入門:OmniAuth、API認証、Authtrailの活用方法

フォームには以下のように入力してください。

  • Application name(アプリケーション名):新しいOAuthアプリに適切な名前を付けます。
  • Homepage url(ホームページURL):今のところはhttps://localhost:3000/を指定しましょう。本番環境では実際のアプリのホームページURLを使用します。
  • Application description(アプリケーションの説明):必須項目ではありませんが、アプリを多数管理している場合は区別するために記載しておくとよいでしょう。
  • Authorization callback url(認証コールバックURL):必須入力項目です。一般的にはhttps://<app-url>/users/auth/<application-provider>/callbackのようなOAuthコールバックURL形式になります。ただし、Googleなどの一部のOAuthプロバイダーはこの形式に従わない場合があるため注意が必要です。

入力が完了したら「Register application」をクリックします。続く画面で新しいアプリシークレットを生成し、安全な場所にメモしておきましょう。シークレットは一度しか表示されないので注意してください。

Deviseイニシャライザーの設定

Deviseイニシャライザーconfig/initializers/devise.rbを開き、GitHub用のOmniAuthセクションへ移動します。おそらくコメントアウトされているので、コメントを外して、新しく作成したGitHub OAuthアプリのIDとシークレットで編集してください。

OmniAuthコールバックコントローラーの作成

すでにDeviseのコントローラーを生成している場合は、OmniauthCallbacksControllerが用意されているので、それを適宜カスタマイズします。存在しない場合は手動で作成し、以下のように編集してください。

上記のコードに関する補足です。

  • from_omniauthは、後ほどUserモデル内に実装するメソッドです。
  • sign_in_and_redirectはOAuth側に用意されているメソッドです。

Userモデルを変更するマイグレーションの追加

次に、Userモデルにproviderカラムとuidカラムを追加する必要があります。

bundle exec rails db:migrateを実行して、このステップを完了させましょう。

DeviseモデルをOmniauthableにする

ここで、UserモデルにDeviseのOmniauthableモジュールを追加して編集します。

続けて、from_omni_authメソッドを追加します。このメソッドは、先ほどセットアップしたUsers::OmniauthCallbacksControllerから呼び出されます。

最後に残った作業は、Deviseのビューにログインリンクを追加することだけです。

ログインリンクの設置

デフォルトでは、Deviseが自動的に適切なプロバイダーのログインリンクをユーザー登録・ログインビューに追加してくれます。ただしこのリンクはGETメソッドを使用します。一方、OmniAuth 2.0以降はPOSTリクエストを推奨しているため、既存のリンクを無効化し、自分でPOSTリクエストを使うリンクを挿入する必要があります。

これで、Ruby on Rails 7アプリにDeviseとGitHub OAuth認証を組み込むことに成功しました。コンパニオンアプリの完全なソースコードも公開されていますので、ぜひ参照してみてください。

続いて、もうひとつの高度なユースケースである「Deviseを使ったAPI認証」を見ていきます。

RubyにおけるDeviseを使ったAPI認証

今日では、ユーザーがAPI経由でアプリに接続できることを期待するのはごく普通のことです。このセクションでは、Deviseを使ってそうしたユーザーリクエストを安全に認証する方法を紹介します。

ブラウザベースの認証は一般にCookieベースですが、API認証の大部分はJSON Web Token(JWT)と呼ばれるトークンによって行われ、ヘッダーを介してやり取りされます。

ヒント:このセクションでは、RailsのAPI専用アプリを扱う前提で進めます。一緒に進めたい方はrails new app_name --apiで新規作成してください。

JWTベースの認証フロー

前述のとおり、API認証はJWTトークンに基づいています。JWTベースの認証フローがどのように行われるのかを理解しておくことが重要です。基本的には、以下のような流れになります。

  • ユーザークライアントがAPIアプリにリクエストを送信します。
  • APIアプリはJSON Web Token(JWT)、つまりCookieの代わりとして使える認証トークンをレスポンスとして返します。
  • 以降、ユーザークライアントからのリクエストには、このトークンをAuthorizationヘッダーに付けて送信します。
  • ユーザーはDeviseの「session destroy」アクションを叩くことで、トークンが破棄され、ログアウト処理が完了します。

それでは、このフローを実際に構築していきましょう。まずはCORS(オリジン間リソース共有)の設定から始めます。

CORSの設定

CORSは、APIアプリが外部ソースからのリクエストを受け入れられるようにする仕組みです。CORSはHTTPベースのセキュリティポリシーで、アプリケーションが外部リクエストをどのように扱うかを定義します。デフォルトでは、最初のリクエスト元とは異なるドメイン(つまり異なる「オリジン」)からのリクエストはブロックされます。

CORSを適切に扱うために、便利なrack-cors gemを使用します。Gemfileで以下の行のコメントを解除し、bundle installを実行してください。

また、対応するCORSイニシャライザーファイルを開き、以下のように修正します。

ここで行った内容について、いくつか重要なポイントがあります。

  • origins "*":APIアプリがあらゆるソースからのリクエストを受け入れられるようにするという意味です。
  • expose: %w[Authorization Uid]:デフォルトではrack-cors gemはAuthorizationヘッダーとUidヘッダーを公開しませんが、認証トークンをやり取りするためにこれらが必要になります。

ここまでできたら、Deviseとそれに付随するDevise-JWT gemをインストールしましょう。

DeviseおよびDevise-JWT gemをRailsアプリに追加する

devise-jwt gemは、JWTトークンを扱えるようにするDeviseの拡張です。gemをGemfileに追加し、bundle installを実行してください。

続いて、Deviseのインストールジェネレーターbundle exec rails g devise:installを実行します。

モデルの生成と設定

ここで2つのモデルをセットアップします。1つは通常のDeviseユーザーモデル(bundle exec rails g devise User)、もう1つは失効戦略(revocation strategy、つまりユーザーがAPIからログアウトする方法)に使用するモデルです。

通常のDeviseユーザーモデルをAPI認証向けに変更するには、JWT token authenticatableモジュールを追加し、トークンの失効戦略として2つ目のモデルJwtDenylistを使用するよう定義します。

次に、2つ目のモデルを設定し、使用する失効戦略と失効テーブルを参照させます。

次のセクションでは、トークンの失効とは何か、なぜそれが必要なのかを説明します。

トークン失効(Token Revocation)の重要性

なぜトークンの失効が重要なのでしょうか?それはJWTトークンがステートレスだからです。サーバーはトークンに署名することしか知らず、その他の状態については何も把握していません。この状況では、サーバーは対応するトークンを取り消すことでユーザーをログアウトさせる術がありません。個々のトークンを失効させる手段がないため、独自に失効の仕組みを構築し、サーバーに使うよう指示する必要があります。

トークンを失効させるとき、内部的にはトークン固有の一部であるjti(JWT ID)が抽出され、定義された失効戦略に従って利用されます。

当然ながら、「トークン失効戦略とは何か」という疑問も生じます。簡単に言えば、それはサーバーがトークンの失効をどのように処理するかの定義です。基本となる失効戦略は3つあります。

  • JTIMatcher戦略:「jti」という一意のカラムをユーザーモデルに追加し、ユーザーモデル自体が失効テーブルも兼ねる方式です。ユーザーがリクエストするたびに、ヘッダー内のjtiが保存されたトークンと照合され、一致が見つかった場合のみアクセスが許可されます。
  • Denylist(拒否リスト)戦略jtiと失効したトークンの有効期限(exp)をデータベーステーブルに保存する方式です。ユーザーがリクエストするたびに、現在のトークンのjtiがデータベース内の失効済みトークンと照合され、一致が見つかればそのユーザーのリクエストは拒否されます。
  • Allowlist(許可リスト)戦略:ある意味で最初の戦略に似ていますが、JWT IDを保存するテーブルが、ユーザートークンを保存する別のテーブルと一対多の関係を持つ点が異なります。リクエストのたびに、Allowlistテーブルに保存されたユーザーのjtiが、トークンを格納する対応テーブルの内容と照合され、一致が見つかった場合のみアクセスが許可されます。

もちろん、これはトークン失効のごく簡略化した概要です。詳細については公式ドキュメントなどを参照してください。

JWTトークン用の署名キーの設定

ユーザーとそのリクエストを安全なトークンで認証するためには、トークンに署名する手段が必要です。そこで登場するのがシークレットキーです。Railsのシークレットキーであるsecret_key_baseとは別の、新しいキーを生成することが推奨されています。

bundle exec rake secretを実行して一意のキーを生成し、Deviseイニシャライザーにこのキーを含めましょう。

最後に、コントローラーをセットアップします。

コントローラーのセットアップ

API認証にDeviseを実装する最後のステップは、コントローラーのセットアップです。シンプルにするため、登録(registrations)用とセッション(sessions)用の2つのコントローラーを用意します。

まずは登録用コントローラーから手動で作成していきます。

このコントローラーのポイントは以下のとおりです。

  • リクエストに対してJSONで応答するよう設定しています。
  • 登録の成功または失敗の結果を返すrespond_withアクションを指定しています。

続いて、セッション用コントローラーです。

登録用コントローラーと同様に、JSONで応答することを指定しています。また、ユーザーのログイン成功時に対応するrespond_withアクションと、ユーザーのログアウトを処理するrespond_to_on_destroyを定義しています。

以上で、DeviseとJWTトークンによるAPI認証フローが動作する状態になりました!

最後のセクションでは、DeviseとAuthtrailを使ったユーザーログインの追跡方法を簡単に見ていきます。

AuthtrailでDeviseのログインを追跡する

たとえば、誰かが自分のアカウントにログインするたびに、IPアドレスやログイン日時などの詳細情報を含んだ通知メールをアプリユーザーに送りたいとします。これをどう実現すればよいでしょうか?

まずユーザーのログインを追跡し、その情報を通知メールに活用する必要があります。ログインの追跡には、Authtrailという便利なgemが利用でき、Deviseとも相性抜群です。

Authtrailのインストール

まずbundle add authtrailでgemをインストールします。加えて、メールアドレスやIPアドレスといった個人を特定可能な情報をアプリのデータベースに保存することになるため、本番環境ではLockboxとBlindindexのgemを組み合わせてデータを暗号化することが強く推奨されます。

次に、Authtrailのジェネレーターを実行して、イニシャライザーとログインデータを保存するテーブルのマイグレーションを作成します。

Authtrailの仕組み

ユーザーがログインを試みるたびに、以下の重要な情報を含む新しいAuthtrailレコードが作成されます。

  • 使用されたログイン用メールアドレス
  • ログインが成功したかどうか
  • ログイン失敗時の理由(失敗した場合)
  • ユーザーのIPアドレスやreferrerなど、その他多くの情報

この情報は自由に活用できます。たとえば、メールアドレスとIPアドレスの情報を添えた通知メールをユーザーに送り、自分のアカウントへのログイン試行があったことを知らせることができます。

Authtrailのドキュメントを読めば、利用可能なすべての可能性を確認できます。

まとめ

このシリーズでは、Devise gemについて深く掘り下げてきました。

第1回では、モジュール、ヘルパー、ビュー、コントローラー、ルーティングの仕組みなど、Deviseの基礎を習得しました。そして今回の第2回(最終回)では、DeviseとOAuthの併用、Authtrailによるログイン追跡、API認証への活用方法を探りました。

このシリーズが、Devise認証に関する頼れるガイドになれば幸いです。

それでは、Happy Coding!

P.S. Ruby Magicの記事を公開と同時にお読みになりたい方は、Ruby Magicニュースレターを購読すれば、記事を見逃すことはありません!

  1. RailsのDeviseマスタリング入門:OmniAuth、API認証、Authtrailの活用方法

    このシリーズの第1回では、サンプルアプリを通じてDeviseのモジュール、ヘルパー、ビュー、コントローラー、ルーティングについて学びました。今回の第2回(最終回)では、より高度なDeviseの使い方として、OmniAuthによるソーシャルログイン、JWTを利用したAPI認証、そしてAuthtrailによるログイン履歴の追跡について詳しく解説していきます。それでは早速始めましょう!RubyでOmniAuthによる認証を実現する現在、ほとんどすべてのWebアプリケーションで、TwitterやFacebookなどのSNS、Google、GitHubなど、多様な認証プロバイダーを使ったログイン機能が提

  2. Rubyの例外処理をマスターする:実践ガイド

    AppSignalでは、Rubyアプリケーション向けのエラー追跡サービスを提供しています。アプリケーションが発生させたすべての例外をキャプチャし、発生した瞬間に開発者へ通知します。 例外処理を正しく実装するのは、実はかなり難しいものです。この記事では、Rubyの例外処理の仕組み、不適切なハンドリングが引き起こす問題、そして例外を適切にrescue(捕捉)する方法について詳しく解説します。 例外をrescueする基本 Rubyで例外をrescueすると、エラーが発生した瞬間にアプリケーションがクラッシュするのを防げます。begin .. rescueブロックを使えば、エラー発生時にアプリケーショ