Skip to content

API認証認可

Next.js(フロントエンド)とRails(バックエンド)を連携させるAPI認証において、主要な2つの方法を比較します。

この方法は、セキュリティを最優先する場合に最適なアプローチです。

仕組み:

  • 認証成功後、JWT(JSON Web Token)を生成し、HttpOnly属性付きのCookieに格納してフロントエンドに送ります。
  • ブラウザは以降のAPIリクエストにこのCookieを自動的に添付します。

メリット:

  • 高いセキュリティ: HttpOnly属性により、JavaScriptからのトークンアクセスを遮断し、XSS(クロスサイト・スクリプティング)攻撃によるトークン盗難のリスクを大幅に減らせます。

デメリット:

  • 実装の複雑性: クロスドメイン通信の場合、CORS設定や認証情報を含むリクエストの制御が必要となり、実装が少し複雑になります。
  • CSRF対策: Cookieを使用するため、別途CSRF対策(SameSite=Laxなど)が必要です。

⚠️ CSRF対策が「SameSite属性」だけで十分か

Section titled “⚠️ CSRF対策が「SameSite属性」だけで十分か”

Cookieを使用する以上、CSRF(クロスサイト・リクエスト・フォージェリ)の脅威は常に付きまといます。

重要な理解: SameSite=Laxは現代のブラウザではデフォルトになりつつありますが、これだけで「CSRF対策完了」とするのは少し危険です。

問題点: 古いブラウザの存在や、サブドメイン間での攻撃など、隙を突かれる可能性があります。

# ❌ 不十分: SameSite属性だけに依存
cookies.signed[:jwt] = {
value: jwt,
httponly: true,
secure: Rails.env.production?,
same_site: :lax # これだけでは不十分
}

改善案: Rails標準のform_authenticity_tokenはAPIモードでは使いにくいですが、カスタムヘッダー(例: X-Requested-With)の存在チェックをサーバー側で行う、あるいはダブルサブミットクッキー法などの追加対策を検討しましょう。

# ✅ 安全: 複数の対策を組み合わせる
# 1. SameSite属性を設定
cookies.signed[:jwt] = {
value: jwt,
httponly: true,
secure: Rails.env.production?,
same_site: :lax
}
# 2. カスタムヘッダーのチェック(ApplicationController)
class ApplicationController < ActionController::API
before_action :verify_csrf_token
private
def verify_csrf_token
# X-Requested-Withヘッダーの存在をチェック
unless request.headers['X-Requested-With'] == 'XMLHttpRequest'
render json: { error: 'Invalid request' }, status: :forbidden
end
end
end
# 3. ダブルサブミットクッキー法(推奨)
# ログイン時にCSRFトークンをCookieに保存
cookies.signed[:csrf_token] = {
value: SecureRandom.hex(32),
httponly: true,
secure: Rails.env.production?,
same_site: :strict
}
# リクエスト時にCookieのCSRFトークンとヘッダーのCSRFトークンを比較
def verify_csrf_token
cookie_token = cookies.signed[:csrf_token]
header_token = request.headers['X-CSRF-Token']
unless cookie_token && cookie_token == header_token
render json: { error: 'Invalid CSRF token' }, status: :forbidden
end
end

推奨されるアプローチ:

  • SameSite属性: 基本的な防御(必須)
  • カスタムヘッダーのチェック: 追加の防御層
  • ダブルサブミットクッキー法: より強力な防御(推奨)

この多層防御により、CSRF攻撃からアプリケーションを守れます。

2. Bearerトークン (localStorage保管)

Section titled “2. Bearerトークン (localStorage保管)”

この方法は、実装のシンプルさと汎用性が大きな利点です。

仕組み:

  • 認証成功後、サーバーはJWTを発行し、JSONレスポンスとしてフロントエンドに返します。
  • フロントエンドは、このトークンをlocalStorageに保存します。
  • APIリクエスト時に、Authorization: Bearer <your_token>というヘッダーを付けてトークンを送信します。

メリット:

  • 高い汎用性: Webブラウザだけでなく、モバイルアプリなど様々なクライアントと共通の認証方式として利用しやすいです。
  • 実装がシンプル: ヘッダーにトークンをセットするだけで、異なるドメイン間でも比較的簡単に実装できます。

デメリット:

  • 低いセキュリティ: localStorageはJavaScriptからアクセス可能なため、XSS攻撃に非常に脆弱です。トークンが盗まれると、その有効期限が切れるまで不正利用されるリスクがあります。
  • セキュリティを最優先するなら、JWT + HttpOnly Cookieを選びましょう。これは、Webアプリケーションにおける現代のベストプラクティスと見なされています。
  • 実装のシンプルさや、Webアプリとモバイルアプリで認証基盤を共通化したい場合は、Bearerトークンも選択肢になります。ただし、その場合はXSS対策(CSPなど)を厳重に行う必要があります。
項目JWT + HttpOnly CookieBearerトークン (localStorage)
セキュリティ最高 🛡️
XSS(クロスサイト・スクリプティング)攻撃に非常に強い。HttpOnly属性により、JavaScriptからのトークンアクセスを完全に遮断。
低い 🚨
XSS攻撃に非常に弱い。localStorageに保存されるため、悪意あるスクリプトによって簡単にトークンが盗まれる。
利便性
ブラウザが自動的にCookieを添付するため、フロントエンドでの手動管理は不要。

JavaScriptからトークンを簡単に取得・設定でき、実装がシンプル。
汎用性
主にブラウザベースのWebアプリケーションに適している。
最高
Web、モバイル、デスクトップなど、様々なクライアントで利用可能。
クロスドメイン複雑
異なるドメイン間での通信には、CORS設定や認証情報の制御が必要。
シンプル
ヘッダーにトークンをセットするだけなので、比較的簡単に通信が可能。
CSRF耐性弱い
Cookieを使用するため、SameSite属性などの追加的な対策が必要。
強い
Authorizationヘッダーを使用するため、CSRFの脅威にさらされにくい。
主なユースケースWebアプリケーション
セキュリティを最優先する場合。
モバイルアプリやSPA
シンプルさや、多様なクライアントへの対応を重視する場合。

結論: Webアプリケーションにおいて、セキュリティを重視するなら「JWT + HttpOnly Cookie」が最善の選択肢です。一方で、モバイルアプリとの連携や実装のシンプルさを優先する場合は「Bearerトークン」も選択肢となりえますが、その際にはXSS対策を厳重に行う必要があります。

Sorceryは、Rails用の認証ライブラリです。他の認証ライブラリ(Deviseなど)と比較して、非常に軽量で、必要に応じて機能をプラグインとして追加できる柔軟性が特徴です。これにより、最小限の機能から始めて、プロジェクトの要件に合わせてカスタマイズできます。

2. Railsプロジェクトのセットアップ

Section titled “2. Railsプロジェクトのセットアップ”

Step 1: Railsプロジェクトの作成

まず、新しいRailsプロジェクトを作成します。Next.jsとの連携を想定しているため、--apiフラグを付けてAPIモードで生成します。

Terminal window
rails new your_app_name --api

Step 2: Gemのインストール

GemfileにSorceryと関連Gemを追加します。jwtはJWTのエンコード/デコード用、sorcery-jwtはSorceryでJWTを扱うためのプラグインです。

# Gemfile
gem 'sorcery'
gem 'sorcery-jwt'
gem 'jwt'

bundle install を実行して、Gemをインストールします。

Terminal window
bundle install

Step 1: Sorceryの初期化

Sorceryをプロジェクトに組み込むためのコマンドを実行します。ここでは、jwtプラグインと、ユーザー認証を可能にするためのcoreプラグインを指定します。

Terminal window
rails g sorcery:install jwt

このコマンドは以下のファイルを生成します。

  • マイグレーションファイル: db/migrate/xxxxxx_sorcery_core.rb が作成され、ユーザーを管理するためのusersテーブルが生成されます。
  • モデルファイル: app/models/user.rb が生成され、Sorceryの機能が組み込まれます。
  • 設定ファイル: config/initializers/sorcery.rb が作成され、Sorceryの全体設定を記述します。

Step 2: データベースのマイグレーション

生成されたマイグレーションファイルを実行して、usersテーブルを作成します。

Terminal window
rails db:migrate

⚠️ Sorceryの設定(セキュリティ強化版)

Section titled “⚠️ Sorceryの設定(セキュリティ強化版)”

config/initializers/sorcery.rbの設定において、以下の「まさかり」ポイントをチェックしてください。

config/initializers/sorcery.rb
Rails.application.config.sorcery.configure do |config|
config.user_config do |user|
# まさかり:ログイン失敗回数の制限(Brute Force対策)を有効にすべき
user.consecutive_login_retries_amount_limit = 5
user.login_lock_time_period = 10.minutes
user.unlock_token_email_method_name = :send_unlock_token_email
end
# まさかり:JWTのアルゴリズムを明示(デフォルトに頼らない)
config.jwt do |jwt|
jwt.algorithm = 'HS256'
end
end

重要な設定ポイント:

  1. ログイン失敗回数の制限: Brute Force攻撃を防ぐため、連続ログイン失敗回数を制限
  2. JWTアルゴリズムの明示: デフォルトに頼らず、使用するアルゴリズムを明示的に指定
  3. パスワードの最小長: セキュリティポリシーに応じて設定
  4. セッションタイムアウト: 適切なタイムアウト時間を設定

この設定により、セキュリティを強化できます。

4. JWT認証の設定とコントローラーの実装

Section titled “4. JWT認証の設定とコントローラーの実装”

Step 1: JWTの秘密鍵の設定

JWTの署名には秘密鍵が必要です。Rails.application.credentialsに安全に保管します。

Terminal window
rails credentials:edit

エディタが開いたら、以下のようにjwt_secret_keyを追加して保存します。

config/credentials.yml.enc
secret_key_base: <自動生成された値>
jwt_secret_key: 'your_strong_and_secret_key' # ここに独自の秘密鍵を設定

Step 2: アプリケーションコントローラーの設定

JWTのペイロードと秘密鍵を定義します。これは、JWTの生成と検証に必要です。

app/controllers/application_controller.rb
class ApplicationController < ActionController::API # APIモードのためActionController::APIを継承
# Sorceryのログイン/ログアウトメソッドを有効にする
include Sorcery::Controller
# JWTのペイロードに含める情報を定義
def jwt_payload_for(user)
{ user_id: user.id }
end
# JWTの秘密鍵を定義
def jwt_secret_key
Rails.application.credentials.jwt_secret_key
end
end

Step 3: 認証コントローラーの作成

ログイン・ログアウト処理を行うコントローラーを作成します。

Terminal window
rails g controller api/v1/sessions create destroy

生成されたファイルに以下のコードを記述します。

  • createアクション: ユーザー名とパスワードで認証し、成功すればJWTを生成してCookieにセットします。
  • destroyアクション: ログアウト処理を行い、CookieからJWTを削除します。
app/controllers/api/v1/sessions_controller.rb
class Api::V1::SessionsController < ApplicationController
def create
user = User.authenticate(params[:email], params[:password])
if user
jwt = sorcery_login(user) # SorceryがJWTを生成
cookies.signed[:jwt] = {
value: jwt,
httponly: true,
secure: Rails.env.production?
}
render json: { message: 'Logged in successfully' }
else
render json: { error: 'Invalid email or password' }, status: :unauthorized
end
end
def destroy
sorcery_logout # Sorceryがユーザーをログアウト
cookies.delete(:jwt, secure: Rails.env.production?)
render json: { message: 'Logged out successfully' }
end
end

Step 4: ルーティングの設定

APIエンドポイントを定義します。

config/routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :sessions, only: [:create] do
delete :destroy, on: :collection
end
end
end
end

Step 1: APIクライアントのセットアップ

Next.js側でAPIと通信するためのクライアント(ここではaxios)を設定します。認証情報(Cookie)を自動で送信するよう設定することが重要です。

// utils/api.js (例)
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:3000/api/v1',
withCredentials: true, // これが重要!
});
export default api;

Step 2: ログイン処理の実装

ログインフォームの送信時に、JWTをJSONで受け取るのではなく、Cookieの保存をブラウザに任せます。

// pages/login.js (例)
import { useState } from 'react';
import api from '../utils/api'; // 先ほど作成したaxiosクライアント
export default function LoginPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleLogin = async (e) => {
e.preventDefault();
try {
await api.post('/sessions', { email, password });
window.location.href = '/dashboard';
} catch (error) {
console.error('Login failed', error);
}
};
return (
// ... フォームのJSX
);
}

Step 3: 認証が必要なAPIへのアクセス

ログイン後、認証が必要なAPIにアクセスする際は、特別なヘッダー設定は不要です。withCredentials: trueの設定により、ブラウザが自動的にCookieを添付してくれます。

// pages/dashboard.js (例)
import { useEffect, useState } from 'react';
import api from '../utils/api';
export default function DashboardPage() {
const [profile, setProfile] = useState(null);
useEffect(() => {
const fetchProfile = async () => {
try {
const response = await api.get('/profile'); // Railsの認証が必要なエンドポイント
setProfile(response.data);
} catch (error) {
console.error('Failed to fetch profile', error);
}
};
fetchProfile();
}, []);
return (
<div>
{profile ? (
<h1>Welcome, {profile.name}!</h1>
) : (
<p>Loading...</p>
)}
</div>
);
}

Step 4: 認証の検証(Rails側)

Rails側では、認証が必要なコントローラーにbefore_action :require_login_from_jwtを追加します。

app/controllers/api/v1/profiles_controller.rb
class Api::V1::ProfilesController < ApplicationController
before_action :require_login_from_jwt
def show
render json: current_user.to_json
end
end

これにより、リクエストヘッダーに有効なJWTがCookieとして含まれているか自動で検証し、current_userがセットされます。無効な場合は401 Unauthorizedエラーが返されます。

Step 2: マイグレーションファイルの追加 リフレッシュトークンをデータベースに保存するためのテーブルを追加します。

Terminal window
rails g model refresh_token token:string user:references
Terminal window
rails db:migrate

Step 3: JWT生成ロジックの変更 JWTのペイロードに有効期限とJTIを含めるようにApplicationControllerを修正します。

app/controllers/application_controller.rb
class ApplicationController < ActionController::API
include Sorcery::Controller
# JWTのペイロードに含める情報を定義
def jwt_payload_for(user)
{
user_id: user.id,
exp: Time.now.to_i + 300, # アクセストークンを5分で期限切れに設定 (例)
jti: SecureRandom.uuid # 一意なJWT ID
}
end
end
#### ⚠️ JWTの「無効化」ができない問題
JWTはステートレス(サーバーが状態を持たない)であるため、発行したトークンをサーバー側から「即座に無効化」するのが難しいという性質があります。
**重要な理解**:
ユーザーがパスワードを変更したり、アカウントが停止されたりしても、アクセストークン(例:有効期限5分)は死にません。
**問題点**:
有効期限内のトークンは、無効化されても使用できてしまいます。
```ruby
# ❌ 問題: ログアウトしてもトークンは有効なまま
class Api::V1::SessionsController < ApplicationController
def destroy
sorcery_logout
cookies.delete(:access_token)
# 問題: 既に発行されたトークンは有効期限まで使える
end
end

改善案: ログアウト時にjti(トークン固有ID)をRedis等のブラックリストに登録し、有効期限内であっても拒否する仕組みが必要です。

# ✅ 安全: JWTブラックリストを実装
class ApplicationController < ActionController::API
include Sorcery::Controller
before_action :check_jwt_blacklist
private
def check_jwt_blacklist
jti = extract_jti_from_token
if jti && Redis.current.exists("jwt_blacklist:#{jti}")
render json: { error: 'Token has been revoked' }, status: :unauthorized
end
end
def extract_jti_from_token
# CookieからJWTを取得し、jtiを抽出
token = cookies.signed[:access_token]
return nil unless token
decoded_token = JWT.decode(token, jwt_secret_key, true, { algorithm: 'HS256' })
decoded_token[0]['jti']
rescue JWT::DecodeError
nil
end
end
# ログアウト時にブラックリストに追加
class Api::V1::SessionsController < ApplicationController
def destroy
jti = extract_jti_from_token
if jti
# トークンの有効期限までブラックリストに保存
token_exp = extract_exp_from_token
ttl = token_exp - Time.now.to_i
Redis.current.setex("jwt_blacklist:#{jti}", ttl, '1') if ttl > 0
end
sorcery_logout
cookies.delete(:access_token)
cookies.delete(:refresh_token)
render json: { message: 'Logged out successfully' }
end
end

推奨されるアプローチ:

  • JWTブラックリスト: Redisにjtiを保存し、有効期限まで保持
  • パスワード変更時: ユーザーの全トークンをブラックリストに追加
  • アカウント停止時: 即座に全トークンを無効化

このアプローチにより、JWTの即座の無効化が可能になります。

def jwt_secret_key Rails.application.credentials.jwt_secret_key end end

Step 4: 認証コントローラーの修正
ログイン時に、アクセストークンとリフレッシュトークンの両方を発行し、Cookieに保存します。
```ruby
# app/controllers/api/v1/sessions_controller.rb
class Api::V1::SessionsController < ApplicationController
def create
user = login(params[:email], params[:password])
if user
# 1. アクセストークンを発行
access_token = sorcery_login(user)
cookies.signed[:access_token] = {
value: access_token,
httponly: true,
secure: Rails.env.production?,
domain: :all # サブドメイン間でCookieを共有
}
# 2. リフレッシュトークンを生成し、DBとCookieに保存
refresh_token = user.refresh_tokens.create!(token: SecureRandom.uuid)
cookies.signed[:refresh_token] = {
value: refresh_token.token,
httponly: true,
secure: Rails.env.production?,
expires: 1.month.from_now, # 1ヶ月後に期限切れ (例)
domain: :all # サブドメイン間でCookieを共有
}
render json: { message: 'Logged in successfully' }
else
render json: { error: 'Invalid email or password' }, status: :unauthorized
end
end
def destroy
sorcery_logout
# ログアウト時に両方のトークンを削除
cookies.delete(:access_token)
cookies.delete(:refresh_token)
render json: { message: 'Logged out successfully' }
end
end

Step 5: リフレッシュエンドポイントの追加 アクセストークンの期限が切れた場合に、新しいトークンを発行するためのエンドポイントを作成します。

app/controllers/api/v1/token_refreshes_controller.rb
class Api::V1::TokenRefreshesController < ApplicationController
def create
refresh_token = cookies.signed[:refresh_token]
# Cookieからリフレッシュトークンを取得し、有効性を検証
if refresh_token_record = RefreshToken.find_by(token: refresh_token)
user = refresh_token_record.user
# 新しいアクセストークンを発行
access_token = sorcery_login(user)
cookies.signed[:access_token] = {
value: access_token,
httponly: true,
secure: Rails.env.production?
}
render json: { message: 'Access token refreshed successfully' }
else
render json: { error: 'Invalid refresh token' }, status: :unauthorized
end
end
end

⚠️ リフレッシュトークンの「再利用」と「無効化」

Section titled “⚠️ リフレッシュトークンの「再利用」と「無効化」”

リフレッシュトークンをDBに保存する設計は素晴らしいですが、運用の懸念があります。

重要な理解: リフレッシュトークンが一度漏洩すると、期限が切れるまで攻撃者が新しいアクセストークンを発行し続けられます。

問題点: トークンの「回転(Rotation)」が考慮されていません。

# ❌ 危険: リフレッシュトークンが再利用可能
class Api::V1::TokenRefreshesController < ApplicationController
def create
refresh_token = cookies.signed[:refresh_token]
refresh_token_record = RefreshToken.find_by(token: refresh_token)
if refresh_token_record
user = refresh_token_record.user
# 新しいアクセストークンを発行(リフレッシュトークンはそのまま)
# 問題: 同じリフレッシュトークンが何度でも使える
end
end
end

改善案: Refresh Token Rotation(リフレッシュトークンを使うたびに、古いものを破棄して新しいリフレッシュトークンを発行する)を導入しましょう。これにより、一度使われた古いトークンが再利用された場合、そのユーザーの全セッションを即座に無効化するなどの高度な防御が可能になります。

# ✅ 安全: Refresh Token Rotationを実装
class Api::V1::TokenRefreshesController < ApplicationController
def create
old_refresh_token = cookies.signed[:refresh_token]
refresh_token_record = RefreshToken.find_by(token: old_refresh_token)
if refresh_token_record
user = refresh_token_record.user
# 1. 古いリフレッシュトークンを削除
refresh_token_record.destroy
# 2. 新しいアクセストークンを発行
access_token = sorcery_login(user)
cookies.signed[:access_token] = {
value: access_token,
httponly: true,
secure: Rails.env.production?
}
# 3. 新しいリフレッシュトークンを発行
new_refresh_token = user.refresh_tokens.create!(token: SecureRandom.uuid)
cookies.signed[:refresh_token] = {
value: new_refresh_token.token,
httponly: true,
secure: Rails.env.production?,
expires: 1.month.from_now
}
render json: { message: 'Access token refreshed successfully' }
else
# 古いトークンが再利用された場合(攻撃の可能性)
# ユーザーの全セッションを無効化
user = User.find_by(id: extract_user_id_from_token(old_refresh_token))
user&.refresh_tokens.destroy_all if user
render json: { error: 'Invalid refresh token' }, status: :unauthorized
end
end
end

推奨されるアプローチ:

  • Refresh Token Rotation: 使用時に新しいトークンへ更新
  • 再利用検知: 古いトークンが再利用された場合、全セッションを無効化
  • ログ記録: 異常なアクセスをログに記録

このアプローチにより、リフレッシュトークンの漏洩による被害を最小限に抑えられます。

Step 6: ルーティングの追加 config/routes.rbにリフレッシュトークン用のエンドポイントを追加します。

config/routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :sessions, only: [:create] do
delete :destroy, on: :collection
end
resources :token_refreshes, only: [:create]
end
end
end
  1. Next.js (フロントエンド) のセットアップ Step 1: APIクライアントの設定 axiosのインターセプターを使って、アクセストークンの期限切れを自動的に検知し、リフレッシュトークンを使って新しいアクセストークンを取得するロジックを実装します。
utils/api.js
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:3000/api/v1',
withCredentials: true,
});
// リクエストが送信される前に実行されるインターセプター
api.interceptors.request.use((config) => {
// ここでは特に何もしない。Cookieはブラウザが自動で添付
return config;
}, (error) => {
return Promise.reject(error);
});
// レスポンスが返された後に実行されるインターセプター
api.interceptors.response.use((response) => {
return response;
}, async (error) => {
const originalRequest = error.config;
// アクセストークンの期限切れエラー(401)かつ、リトライがまだの場合
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
// リフレッシュトークンを使って新しいアクセストークンをリクエスト
await axios.post('http://localhost:3000/api/v1/token_refreshes', null, { withCredentials: true });
// 新しいアクセストークンで元のリクエストを再試行
return api(originalRequest);
} catch (refreshError) {
// リフレッシュトークンも無効な場合、ログアウト処理を行う
console.error('Refresh token failed:', refreshError);
// ユーザーをログインページにリダイレクトするなど
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
});
export default api;

Step 2: ログイン処理の実装 ログインフォームのコードは前回と同じです。ブラウザが自動的に2つのCookieを保存します。

この仕組みにより、ユーザーは長期間ログイン状態を維持でき、同時にセキュリティ上のリスクも軽減できます。

⚠️ APIドメインとNext.jsドメインの不一致(CORS)

Section titled “⚠️ APIドメインとNext.jsドメインの不一致(CORS)”

実務ではapi.example.com(Rails)とexample.com(Next.js)のようにドメインが分かれることが多いです。

重要な理解: Cookieのdomain属性を適切に設定しないと、サブドメイン間でCookieが共有されず、ログインできないというトラブルが多発します。

問題点: デフォルトの設定では、api.example.comで設定したCookieはexample.comからは読み取れません。

# ❌ 問題: サブドメイン間でCookieが共有されない
cookies.signed[:access_token] = {
value: access_token,
httponly: true,
secure: Rails.env.production?
# domainが指定されていないため、api.example.comでのみ有効
}

改善案: domain: :all(または特定のドメイン)をcookies.signedのオプションに含める設定を追記しましょう。

# ✅ 安全: サブドメイン間でCookieを共有
cookies.signed[:access_token] = {
value: access_token,
httponly: true,
secure: Rails.env.production?,
domain: :all # すべてのサブドメインで有効
# または
# domain: '.example.com' # 特定のドメインとそのサブドメインで有効
}

推奨される設定:

  • 開発環境: domain: :allまたはdomain: '.localhost'(注意が必要)
  • 本番環境: domain: '.example.com'(実際のドメインを指定)

CORS設定との連携:

config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins ENV['FRONTEND_URL'] || 'http://localhost:3000'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head],
credentials: true # Cookieを使用する場合に必須
end
end

この設定により、サブドメイン間でCookieが正しく共有され、ログイン機能が正常に動作します。

🚀 設計レビューでの「まさかり」文例

Section titled “🚀 設計レビューでの「まさかり」文例”

実務でのコードレビューで使用できる、具体的な指摘文例を以下に示します。

リフレッシュトークンの漏洩対策が不十分な場合の指摘

Section titled “リフレッシュトークンの漏洩対策が不十分な場合の指摘”
【指摘】リフレッシュトークンの漏洩対策が不十分です。
【問題】リフレッシュトークンが盗まれた際、被害に気づく仕組みや被害を最小限に抑える仕組み(Rotation)がありません。
【影響】攻撃者が長期間、被害者のアカウントを乗っ取り続けることが可能になります。
【推奨】リフレッシュトークンの使用時に新しいトークンへ更新する「Rotation」を実装し、古いトークンの不正利用を検知できるようにしてください。
【指摘】CSRF対策がSameSite属性のみに依存しています。
【問題】古いブラウザやサブドメイン間での攻撃に対して脆弱です。
【影響】CSRF攻撃により、ユーザーが意図しない操作を実行される可能性があります。
【推奨】ダブルサブミットクッキー法やカスタムヘッダーのチェックなどの追加対策を実装してください。

JWTの無効化ができない場合の指摘

Section titled “JWTの無効化ができない場合の指摘”
【指摘】JWTの無効化機能が実装されていません。
【問題】ログアウトやパスワード変更後も、有効期限内のトークンが使用可能です。
【影響】トークンが漏洩した場合、有効期限まで不正利用される可能性があります。
【推奨】jti(トークン固有ID)をRedisのブラックリストに登録し、有効期限内でも拒否できる仕組みを実装してください。

これらの指摘文例を参考に、コードレビューで適切なフィードバックを行い、堅牢なアプリケーションを構築しましょう。