Contents
Rails 7 APIモード 認証 実装 手順:JWTベースの無状態認証を実現する具体的な手順
Rails 7 APIモードにおいて、セキュアでスケーラブルな認証機能を実装したいと考えているエンジニア向けに、JWT(JSON Web Token)を活用した無状態認証の実装手順を解説します。本記事では、プロジェクト初期設定からテストコードまで、ステップバイステップで具体的な実装方法をお伝えします。
Rails 7 APIモードの初期設定
Rails 7 APIモードは、リクエスト処理に特化した軽量なアプリケーション構成を提供しています。このセクションでは、プロジェクト生成から必要なライブラリの導入までを順を追って説明します。
APIモード用のプロジェクト生成手順
Rails 7でAPI専用のプロジェクトを作成するには、以下のコマンドを使用します。--apiオプションを指定することで、ビュー関連のGemが自動的に除外され、軽量な構造になります。
-
新規プロジェクトの作成:
bash
rails new my_api_app --api
cd my_api_app -
データベース設定:
config/database.ymlに適切なデータベース接続情報を記入し、rails db:createでデータベースを作成します。 -
アプリケーションの起動確認:
bash
rails server
ブラウザで http://localhost:3000 にアクセスして、アプリケーションが正常に動作することを確認してください。
必要ライブラリ(JWTなど)のインストール
無状態認証には jwt Gemが必要です。Gemfileに以下のように追加し、bundle installでインストールします。
gem 'jwt'
このライブラリは、トークンの発行・署名・検証を簡潔に実装できます。また、認証フローでのセキュリティ対策(例:有効期限設定)にも活用されます。
無状態認証の選択理由とJWTの特徴
Deviseなどのセッションベースの認証と比較して、JWTの無状態アーキテクチャはAPI開発において多くの利点を提供します。ここではその理由を解説します。
Deviseとの主な違い
| 項目 | Devise(セッション管理) | JWT(無状態認証) |
|---|---|---|
| セッション保存場所 | サーバー側でセッション情報を保持 | トークンをクライアントに保管 |
| スケール可能性 | スケールが難しい(セッション共有が必要) | マルチノード環境でも簡単に対応可能 |
| パフォーマンス | セッション読み込みのオーバヘッドがある | トークン検証は軽量で高速 |
Deviseはユーザー認証に強みがありますが、API開発にはセッション管理の重さや状態保持の制限が課題となります。一方、JWTはクライアントがトークンを保持し、サーバー側で検証する仕組みなので、ロードバランサー付きのクラウド環境でも安定します。
ステートレスアーキテクチャの利点
無状態認証は、以下の3つの主なメリットを持ちます。
- 負荷軽減: サーバー側にセッションデータを保存する必要がないため、スケールが容易です。
- ステートレス性による信頼性向上: マルチノード環境での障害対応やロードバランサーの導入が自然です。
- JWTの自己完結性: トークン内にユーザーIDや有効期限などが必要な情報を含むため、リソースを共有する際の処理を簡略化できます。
トークン認証の実装フロー
ここでは、ユーザー認証時のトークン発行とリクエスト検証を実現する具体的なコード例を紹介します。
ユーザー認証時のトークン発行処理
SessionsControllerに以下のメソッドを追加し、ログイン時にJWTトークンを発行します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
def create user = User.find_by(email: params[:email]) if user&.valid_password?(params[:password]) token = encode_token(user.id) render json: { token: token }, status: :ok else render json: { error: 'Invalid email or password' }, status: :unauthorized end end private def encode_token(payload) payload[:exp] = 1.hour.from_now.to_i # 有効期限を設定 JWT.encode(payload, Rails.application.secrets.secret_key_base, 'HS256') end |
このコードでは、Userモデルのvalid_password?メソッドでパスワードを検証し、一致すればトークンを発行します。encode_tokenはjwtライブラリを使用して署名付きトークンを作成しています。
リクエスト検証用のミドルウェア構築
保護されたAPIエンドポイントでは、HTTPヘッダーにAuthorization: Bearer <token>が含まれているかをチェックします。以下のコード例で実現できます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class ApplicationController < ActionController::Base before_action :authenticate_user private def authenticate_user header = request.headers['Authorization'] return render json: { error: 'Missing token' }, status: :unauthorized unless header token = header.split(' ').last begin decoded_token = JWT.decode(token, Rails.application.secrets.secret_key_base, true, algorithm: 'HS256') @current_user = User.find(decoded_token[0]['user_id']) rescue JWT::DecodeError render json: { error: 'Invalid token' }, status: :unauthorized end end end |
before_actionでリクエストごとに認証チェックを行い、トークンが有効であれば@current_userにユーザー情報をセットします。
セキュリティ強化のベストプラクティス
JWTは軽量ですが、不正利用を防ぐためのセキュリティ対策が不可欠です。
HTTPSの必須化設定
API通信では必ずHTTPSを使用してください。config/environments/production.rbに以下のように設定します。
|
1 2 |
config.force_ssl = true |
注意: 開発環境でこの設定を行うと、localhostへのHTTPリクエストが失敗する可能性があります。開発では一時的に無効化し、本番環境でのみ有効にすることが推奨されます。
トークン有効期限とリフレッシュメカニズム
JWTは発行時刻(iat)と有効期限(exp)を持つことで、不正アクセスを防止できます。以下のようにトークンにエクスプレイドタイムを追加します。
|
1 2 3 4 5 |
def encode_token(payload) payload[:exp] = 1.hour.from_now.to_i # 1時間の有効期間 JWT.encode(payload, Rails.application.secrets.secret_key_base, 'HS256') end |
リフレッシュトークンの実装例
リフレッシュトークンは、アクセストークンが切れた際に再発行するための仕組みです。以下に基本的な実装例を示します:
- リフレッシュトークンをデータベースに保存(
refresh_tokensテーブル等) - アクセストークンの有効期限が切れたら、リフレッシュトークンを使用して新しいアクセストークンを発行
- リフレッシュトークンも有効期限を持たせ、定期的に削除する
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# リフレッシュトークン生成処理 def generate_refresh_token(user) payload = { user_id: user.id, exp: 7.days.from_now.to_i } JWT.encode(payload, Rails.application.secrets.secret_key_base, 'HS256') end # リフレッシュトークンの検証処理 def verify_refresh_token(token) begin decoded = JWT.decode(token, Rails.application.secrets.secret_key_base, true, algorithm: 'HS256') user_id = decoded[0]['user_id'] # データベースでリフレッシュトークンが存在し、有効期限内かを確認 return User.find(user_id) if valid_refresh_token?(token) rescue JWT::DecodeError nil end end |
トークン署名検証時のalgorithm指定の注意点
JWT.decodeでalgorithm: 'HS256'を明示的に指定している場合、デフォルト値(RS256等)が異なっていたり、想定外の暗号化アルゴリズムを許可してしまう可能性があります。実装時に以下の点に注意してください:
algorithm: 'HS256'はHMAC-SHA256を指定し、安全な選択肢です。- 他のアルゴリズム(例:
'RS256')を使用する場合は、公開鍵の導入・管理が必要です。
テストコードによる動作検証
最後に、実装した認証フローをテストする方法を説明します。
RSpecでの認証フローテスト
spec/controllers/sessions_controller_spec.rbに以下のコードを記述して、トークン発行と認証の検証を行います。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
require 'rails_helper' RSpec.describe SessionsController, type: :controller do describe '#create' do let(:user) { create(:user) } context '有効なメールとパスワードが送信されたとき' do it 'トークンを返す' do post :create, params: { email: user.email, password: 'password123' } expect(response).to have_http_status(:ok) expect(JSON.parse(response.body)['token']).not_to be_nil end end context '無効なメールまたはパスワードが送信されたとき' do it 'エラーレスポンスを返す' do post :create, params: { email: user.email, password: 'wrongpassword' } expect(response).to have_http_status(:unauthorized) expect(JSON.parse(response.body)['error']).to eq('Invalid email or password') end end end end |
このテストでは、有効な認証情報が送信された際の処理と無効な場合の検証を行います。
Postmanでの手動検証例
Postmanでトークン発行API(POST /sessions)を呼び出し、レスポンスに返却されたトークンを使って保護されたエンドポイント(例:GET /api/users)にアクセスします。
- 認証トークン取得:
- Method: POST
- URL:
http://localhost:3000/sessions -
Body:
{ "email": "[メールアドレス削除]", "password": "password123" } -
保護されたエンドポイント呼び出し:
- Method: GET
- URL:
http://localhost:3000/api/users - Headers:
Authorization: Bearer <取得したトークン>
結論
本記事では、Rails 7 APIモードにおけるJWTベースの無状態認証実装手順をステップバイステップで解説しました。ポイントは以下の通りです:
- Rails 7 APIモードの初期設定と必要ライブラリ(
jwt)の導入方法 - Deviseと比較したJWT無状態アーキテクチャの利点
- ユーザー認証時のトークン発行・検証処理の実装コード
- HTTPSやトークン有効期限などセキュリティ対策のベストプラクティス
- RSpecやPostmanでの認証フローのテスト方法
この手順に従うことで、信頼性と安全性を確保したAPI開発が可能です。実装に際しては、HTTPS導入やトークン管理の見直しも忘れずに行い、プロジェクトのセキュリティ体制を整えてください。