API認証認可
🔑 API認証・認可の比較
Section titled “🔑 API認証・認可の比較”Next.js(フロントエンド)とRails(バックエンド)を連携させるAPI認証において、主要な2つの方法を比較します。
1. JWT + HttpOnly Cookie
Section titled “1. JWT + HttpOnly Cookie”この方法は、セキュリティを最優先する場合に最適なアプローチです。
仕組み:
- 認証成功後、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 endend
# 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 endend推奨されるアプローチ:
- SameSite属性: 基本的な防御(必須)
- カスタムヘッダーのチェック: 追加の防御層
- ダブルサブミットクッキー法: より強力な防御(推奨)
この多層防御により、CSRF攻撃からアプリケーションを守れます。
2. Bearerトークン (localStorage保管)
Section titled “2. Bearerトークン (localStorage保管)”この方法は、実装のシンプルさと汎用性が大きな利点です。
仕組み:
- 認証成功後、サーバーはJWTを発行し、JSONレスポンスとしてフロントエンドに返します。
- フロントエンドは、このトークンをlocalStorageに保存します。
- APIリクエスト時に、
Authorization: Bearer <your_token>というヘッダーを付けてトークンを送信します。
メリット:
- 高い汎用性: Webブラウザだけでなく、モバイルアプリなど様々なクライアントと共通の認証方式として利用しやすいです。
- 実装がシンプル: ヘッダーにトークンをセットするだけで、異なるドメイン間でも比較的簡単に実装できます。
デメリット:
- 低いセキュリティ: localStorageはJavaScriptからアクセス可能なため、XSS攻撃に非常に脆弱です。トークンが盗まれると、その有効期限が切れるまで不正利用されるリスクがあります。
どちらを選ぶべきか?
Section titled “どちらを選ぶべきか?”- セキュリティを最優先するなら、JWT + HttpOnly Cookieを選びましょう。これは、Webアプリケーションにおける現代のベストプラクティスと見なされています。
- 実装のシンプルさや、Webアプリとモバイルアプリで認証基盤を共通化したい場合は、Bearerトークンも選択肢になります。ただし、その場合はXSS対策(CSPなど)を厳重に行う必要があります。
🛡️ API認証方式の比較
Section titled “🛡️ API認証方式の比較”| 項目 | JWT + HttpOnly Cookie | Bearerトークン (localStorage) |
|---|---|---|
| セキュリティ | 最高 🛡️ XSS(クロスサイト・スクリプティング)攻撃に非常に強い。HttpOnly属性により、JavaScriptからのトークンアクセスを完全に遮断。 | 低い 🚨 XSS攻撃に非常に弱い。localStorageに保存されるため、悪意あるスクリプトによって簡単にトークンが盗まれる。 |
| 利便性 | 中 ブラウザが自動的にCookieを添付するため、フロントエンドでの手動管理は不要。 | 高 JavaScriptからトークンを簡単に取得・設定でき、実装がシンプル。 |
| 汎用性 | 中 主にブラウザベースのWebアプリケーションに適している。 | 最高 Web、モバイル、デスクトップなど、様々なクライアントで利用可能。 |
| クロスドメイン | 複雑 異なるドメイン間での通信には、CORS設定や認証情報の制御が必要。 | シンプル ヘッダーにトークンをセットするだけなので、比較的簡単に通信が可能。 |
| CSRF耐性 | 弱い Cookieを使用するため、SameSite属性などの追加的な対策が必要。 | 強い Authorizationヘッダーを使用するため、CSRFの脅威にさらされにくい。 |
| 主なユースケース | Webアプリケーション セキュリティを最優先する場合。 | モバイルアプリやSPA シンプルさや、多様なクライアントへの対応を重視する場合。 |
結論: Webアプリケーションにおいて、セキュリティを重視するなら「JWT + HttpOnly Cookie」が最善の選択肢です。一方で、モバイルアプリとの連携や実装のシンプルさを優先する場合は「Bearerトークン」も選択肢となりえますが、その際にはXSS対策を厳重に行う必要があります。
1. Sorceryとは?
Section titled “1. Sorceryとは?”Sorceryは、Rails用の認証ライブラリです。他の認証ライブラリ(Deviseなど)と比較して、非常に軽量で、必要に応じて機能をプラグインとして追加できる柔軟性が特徴です。これにより、最小限の機能から始めて、プロジェクトの要件に合わせてカスタマイズできます。
2. Railsプロジェクトのセットアップ
Section titled “2. Railsプロジェクトのセットアップ”Step 1: Railsプロジェクトの作成
まず、新しいRailsプロジェクトを作成します。Next.jsとの連携を想定しているため、--apiフラグを付けてAPIモードで生成します。
rails new your_app_name --apiStep 2: Gemのインストール
GemfileにSorceryと関連Gemを追加します。jwtはJWTのエンコード/デコード用、sorcery-jwtはSorceryでJWTを扱うためのプラグインです。
# Gemfilegem 'sorcery'gem 'sorcery-jwt'gem 'jwt'bundle install を実行して、Gemをインストールします。
bundle install3. Sorceryのセットアップ
Section titled “3. Sorceryのセットアップ”Step 1: Sorceryの初期化
Sorceryをプロジェクトに組み込むためのコマンドを実行します。ここでは、jwtプラグインと、ユーザー認証を可能にするためのcoreプラグインを指定します。
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テーブルを作成します。
rails db:migrate⚠️ Sorceryの設定(セキュリティ強化版)
Section titled “⚠️ Sorceryの設定(セキュリティ強化版)”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' endend重要な設定ポイント:
- ログイン失敗回数の制限: Brute Force攻撃を防ぐため、連続ログイン失敗回数を制限
- JWTアルゴリズムの明示: デフォルトに頼らず、使用するアルゴリズムを明示的に指定
- パスワードの最小長: セキュリティポリシーに応じて設定
- セッションタイムアウト: 適切なタイムアウト時間を設定
この設定により、セキュリティを強化できます。
4. JWT認証の設定とコントローラーの実装
Section titled “4. JWT認証の設定とコントローラーの実装”Step 1: JWTの秘密鍵の設定
JWTの署名には秘密鍵が必要です。Rails.application.credentialsに安全に保管します。
rails credentials:editエディタが開いたら、以下のようにjwt_secret_keyを追加して保存します。
secret_key_base: <自動生成された値>jwt_secret_key: 'your_strong_and_secret_key' # ここに独自の秘密鍵を設定Step 2: アプリケーションコントローラーの設定
JWTのペイロードと秘密鍵を定義します。これは、JWTの生成と検証に必要です。
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 endendStep 3: 認証コントローラーの作成
ログイン・ログアウト処理を行うコントローラーを作成します。
rails g controller api/v1/sessions create destroy生成されたファイルに以下のコードを記述します。
- createアクション: ユーザー名とパスワードで認証し、成功すればJWTを生成してCookieにセットします。
- destroyアクション: ログアウト処理を行い、CookieからJWTを削除します。
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' } endendStep 4: ルーティングの設定
APIエンドポイントを定義します。
Rails.application.routes.draw do namespace :api do namespace :v1 do resources :sessions, only: [:create] do delete :destroy, on: :collection end end endend5. Next.jsとの連携
Section titled “5. Next.jsとの連携”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を追加します。
class Api::V1::ProfilesController < ApplicationController before_action :require_login_from_jwt
def show render json: current_user.to_json endendこれにより、リクエストヘッダーに有効なJWTがCookieとして含まれているか自動で検証し、current_userがセットされます。無効な場合は401 Unauthorizedエラーが返されます。
Step 2: マイグレーションファイルの追加 リフレッシュトークンをデータベースに保存するためのテーブルを追加します。
rails g model refresh_token token:string user:referencesrails db:migrateStep 3: JWT生成ロジックの変更 JWTのペイロードに有効期限とJTIを含めるようにApplicationControllerを修正します。
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 } endend
#### ⚠️ JWTの「無効化」ができない問題
JWTはステートレス(サーバーが状態を持たない)であるため、発行したトークンをサーバー側から「即座に無効化」するのが難しいという性質があります。
**重要な理解**:ユーザーがパスワードを変更したり、アカウントが停止されたりしても、アクセストークン(例:有効期限5分)は死にません。
**問題点**:有効期限内のトークンは、無効化されても使用できてしまいます。
```ruby# ❌ 問題: ログアウトしてもトークンは有効なままclass Api::V1::SessionsController < ApplicationController def destroy sorcery_logout cookies.delete(:access_token) # 問題: 既に発行されたトークンは有効期限まで使える endend改善案:
ログアウト時に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 endend
# ログアウト時にブラックリストに追加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' } endend推奨されるアプローチ:
- 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' } endendStep 5: リフレッシュエンドポイントの追加 アクセストークンの期限が切れた場合に、新しいトークンを発行するためのエンドポイントを作成します。
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 endend⚠️ リフレッシュトークンの「再利用」と「無効化」
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 endend改善案: 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 endend推奨されるアプローチ:
- Refresh Token Rotation: 使用時に新しいトークンへ更新
- 再利用検知: 古いトークンが再利用された場合、全セッションを無効化
- ログ記録: 異常なアクセスをログに記録
このアプローチにより、リフレッシュトークンの漏洩による被害を最小限に抑えられます。
Step 6: ルーティングの追加 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 endend- Next.js (フロントエンド) のセットアップ Step 1: APIクライアントの設定 axiosのインターセプターを使って、アクセストークンの期限切れを自動的に検知し、リフレッシュトークンを使って新しいアクセストークンを取得するロジックを実装します。
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設定との連携:
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を使用する場合に必須 endendこの設定により、サブドメイン間でCookieが正しく共有され、ログイン機能が正常に動作します。
🚀 設計レビューでの「まさかり」文例
Section titled “🚀 設計レビューでの「まさかり」文例”実務でのコードレビューで使用できる、具体的な指摘文例を以下に示します。
リフレッシュトークンの漏洩対策が不十分な場合の指摘
Section titled “リフレッシュトークンの漏洩対策が不十分な場合の指摘”【指摘】リフレッシュトークンの漏洩対策が不十分です。【問題】リフレッシュトークンが盗まれた際、被害に気づく仕組みや被害を最小限に抑える仕組み(Rotation)がありません。【影響】攻撃者が長期間、被害者のアカウントを乗っ取り続けることが可能になります。【推奨】リフレッシュトークンの使用時に新しいトークンへ更新する「Rotation」を実装し、古いトークンの不正利用を検知できるようにしてください。CSRF対策が不十分な場合の指摘
Section titled “CSRF対策が不十分な場合の指摘”【指摘】CSRF対策がSameSite属性のみに依存しています。【問題】古いブラウザやサブドメイン間での攻撃に対して脆弱です。【影響】CSRF攻撃により、ユーザーが意図しない操作を実行される可能性があります。【推奨】ダブルサブミットクッキー法やカスタムヘッダーのチェックなどの追加対策を実装してください。JWTの無効化ができない場合の指摘
Section titled “JWTの無効化ができない場合の指摘”【指摘】JWTの無効化機能が実装されていません。【問題】ログアウトやパスワード変更後も、有効期限内のトークンが使用可能です。【影響】トークンが漏洩した場合、有効期限まで不正利用される可能性があります。【推奨】jti(トークン固有ID)をRedisのブラックリストに登録し、有効期限内でも拒否できる仕組みを実装してください。これらの指摘文例を参考に、コードレビューで適切なフィードバックを行い、堅牢なアプリケーションを構築しましょう。