【2025年版】Rails 8とLaravel 12のセキュリティ実装比較 | 認証・CSRF・XSS対策のコード例

RailsからLaravelを眺める

※本記事は「RailsからLaravelを眺める」シリーズの第13回です。Rails出身の私がLaravelを触りながら、Railsと比較して違いを整理していく連載になります。

はじめに

今回のテーマはセキュリティ実装です。Railsでは当たり前だった「デフォルトで安全」という考え方が、Laravelではどのように実現されているのか。認証、CSRF対策、XSS対策など、Webアプリケーションの基本的なセキュリティ機能について、両フレームワークの実装方法を比較してみました。

Rails 8は「No Build」哲学でさらにシンプルな方向へ進化し、Laravel 12はエコシステムとの統合を強化。この設計思想の違いが、セキュリティ実装にどう表れるのか、実際のコードを書きながら確認していきます。

認証システムの実装比較

Rails 8 – Authentication Zero とシンプルさの追求

Rails 8では、DHHが提唱する「No Build」哲学に基づき、Authentication Zeroがデフォルトの認証ソリューションとして推奨されています。

# Gemfile
gem 'authentication-zero'

# 生成コマンド
rails generate authentication

生成される認証コントローラーは驚くほどシンプル:

class SessionsController < ApplicationController
  def create
    user = User.authenticate_by(
      email: params[:email],
      password: params[:password]
    )
    
    if user
      login user
      redirect_to root_path, notice: "Welcome back!"
    else
      flash.now[:alert] = "Invalid email or password"
      render :new, status: :unprocessable_entity
    end
  end
  
  private
  
  def login(user)
    session[:user_id] = user.id
    user.regenerate_session_token
  end
end

Laravel 12 – Breeze とエコシステムの充実

Laravel 12では、Laravel Breezeが軽量な認証スターターキットとして人気です。

composer require laravel/breeze --dev
php artisan breeze:install

Laravelの認証コントローラーは、より多機能:

class AuthenticatedSessionController extends Controller
{
    public function store(LoginRequest $request): RedirectResponse
    {
        $request->authenticate();
        
        $request->session()->regenerate();
        
        // Rate limiting が標準実装
        RateLimiter::clear($this->throttleKey($request));
        
        return redirect()->intended(route('dashboard'));
    }
    
    protected function throttleKey(Request $request): string
    {
        return Str::lower($request->input('email')).'|'.$request->ip();
    }
}

比較ポイント:

  • Rails: シンプルさを重視、必要最小限の実装
  • Laravel: 機能充実、Rate Limitingなどが標準装備

CSRF対策の実装

Rails 8

Rails 8では、CSRF対策がデフォルトで有効化されており、ApplicationControllerで自動的に適用されます。

class ApplicationController < ActionController::Base
  # デフォルトで有効、カスタマイズも簡単
  protect_from_forgery with: :exception
  
  # APIモードでは異なる戦略を選択可能
  protect_from_forgery with: :null_session, if: -> { request.format.json? }
end

フォームヘルパーが自動的にトークンを挿入:

<%= form_with model: @user do |f| %>
  <!-- csrf_token が自動挿入される -->
  <%= f.email_field :email %>
  <%= f.submit %>
<% end %>

Laravel 12

LaravelもCSRF保護が標準で有効ですが、ミドルウェアベースのアプローチ:

// app/Http/Kernel.php
protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\VerifyCsrfToken::class,
        // ...
    ],
];

// 除外したいルートを指定可能
class VerifyCsrfToken extends Middleware
{
    protected $except = [
        'stripe/webhook',
        'api/*'  // API routes を除外
    ];
}

Bladeテンプレートでの使用:

<form method="POST" action="/profile">
    @csrf  {{-- CSRFトークンフィールドを自動生成 --}}
    <input type="email" name="email">
    <button type="submit">Update</button>
</form>

SQLインジェクション対策

Rails 8 – Active Record

Rails 8のActive Recordは、プリペアドステートメントを自動的に使用:

# 安全:パラメータ化されたクエリ
User.where(email: params[:email])

# 危険:直接的な文字列展開(警告が出る)
User.where("email = '#{params[:email]}'")

# 安全な raw SQL の使用
User.where("created_at > ?", 1.week.ago)

# Rails 8 の新機能:より安全な sanitize_sql
User.where(
  User.sanitize_sql_for_conditions(
    ["name = :name", { name: params[:name] }]
  )
)

Laravel 12 – Eloquent ORM

Laravel 12のEloquentも同様に安全なクエリビルダーを提供:

// 安全:パラメータバインディング
User::where('email', $request->email)->first();

// 危険:生のクエリ(避けるべき)
DB::select("SELECT * FROM users WHERE email = '{$request->email}'");

// 安全な raw クエリの使用
DB::select('SELECT * FROM users WHERE created_at > ?', [
    Carbon::now()->subWeek()
]);

// Laravel 12 の新機能:より厳格な型チェック
User::query()
    ->where('email', $request->validated('email'))
    ->whereDate('created_at', '>', now()->subWeek())
    ->first();

XSS対策の実装

Rails 8

Rails 8では、ビューでの出力は自動的にエスケープ:

<!-- 自動的にエスケープされる -->
<%= @user.bio %>

<!-- HTMLとして出力したい場合は明示的に -->
<%= @user.bio.html_safe %>
<!-- または -->
<%= raw @user.bio %>

<!-- Rails 8の新機能:Content Security Policy ヘルパー -->
<%= content_security_policy do |policy| %>
  <% policy.default_src :self, :https %>
  <% policy.script_src :self, :https, :unsafe_inline %>
<% end %>

Laravel 12

Laravelも同様に自動エスケープが標準:

{{-- 自動的にエスケープ --}}
{{ $user->bio }}

{{-- エスケープなしで出力(注意が必要) --}}
{!! $user->bio !!}

{{-- Laravel 12の新機能:CSP ディレクティブ --}}
@push('csp')
    <meta http-equiv="Content-Security-Policy" 
          content="default-src 'self'; script-src 'self' 'unsafe-inline'">
@endpush

API認証とRate Limiting

Rails 8 – ActionController::API

# app/controllers/api/base_controller.rb
class Api::BaseController < ActionController::API
  include ActionController::HttpAuthentication::Token::ControllerMethods
  
  before_action :authenticate
  
  # Rails 8 の新機能:built-in rate limiting
  rate_limit to: 100, within: 1.hour, 
             by: -> { request.remote_ip }
  
  private
  
  def authenticate
    authenticate_or_request_with_http_token do |token, options|
      ApiKey.active.find_by(token: token)
    end
  end
end

Laravel 12 – Sanctum

// routes/api.php
Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
    Route::get('/user', function (Request $request) {
        return $request->user();
    });
});

// Laravel 12 の改善された Rate Limiting
RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)->by(
        $request->user()?->id ?: $request->ip()
    )->response(function () {
        return response()->json([
            'message' => 'Too many requests',
        ], 429);
    });
});

パスワードハッシング

Rails 8

class User < ApplicationRecord
  has_secure_password
  
  # Rails 8:Argon2 がデフォルトに
  # bcrypt から移行する場合
  self.password_digest_hash_function = :argon2
  
  # パスワード強度の検証
  validates :password, 
    length: { minimum: 12 },
    format: {
      with: /\A(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])/,
      message: "must include uppercase, lowercase, digit and special character"
    }
end

Laravel 12

use Illuminate\Support\Facades\Hash;

class User extends Authenticatable
{
    // Laravel 12:Argon2id がデフォルト
    protected function casts(): array
    {
        return [
            'password' => 'hashed',
        ];
    }
    
    // パスワードハッシングのカスタマイズ
    public function setPasswordAttribute($value)
    {
        $this->attributes['password'] = Hash::make($value, [
            'memory' => 2048,
            'time' => 4,
            'threads' => 2,
        ]);
    }
}

セキュリティヘッダーの設定

Rails 8

# config/application.rb
class Application < Rails::Application
  config.force_ssl = true
  
  # Rails 8 の新しいセキュリティヘッダー設定
  config.content_security_policy do |policy|
    policy.default_src :self, :https
    policy.font_src    :self, :https, :data
    policy.img_src     :self, :https, :data
    policy.object_src  :none
    policy.script_src  :self, :https
    policy.style_src   :self, :https, :unsafe_inline
  end
  
  # Permissions Policy(新機能)
  config.permissions_policy do |policy|
    policy.camera      :none
    policy.gyroscope   :none
    policy.microphone  :none
    policy.usb         :none
  end
end

Laravel 12

// app/Http/Middleware/SecurityHeaders.php
class SecurityHeaders
{
    public function handle($request, Closure $next)
    {
        $response = $next($request);
        
        $response->headers->set('X-Content-Type-Options', 'nosniff');
        $response->headers->set('X-Frame-Options', 'DENY');
        $response->headers->set('X-XSS-Protection', '1; mode=block');
        $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
        
        // Laravel 12: より簡潔な CSP 設定
        $response->headers->set('Content-Security-Policy', 
            "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
        );
        
        return $response;
    }
}

どちらを選ぶべきか?

Rails 8 を選ぶべき場合

  • シンプルさを重視:「設定より規約」の哲学で、デフォルトで十分なセキュリティ
  • 少人数チーム:学習曲線が緩やか、すぐに安全なアプリが作れる
  • Ruby エコシステム:既存のRubyアセットを活用したい

Laravel 12 を選ぶべき場合

  • 柔軟性を重視:細かいカスタマイズが可能
  • エンタープライズ環境:多様な認証方式(OAuth、SAML等)への対応が容易
  • PHP エコシステム:既存のPHPライブラリとの統合

まとめ

Rails 8とLaravel 12、どちらも2025年のWebアプリケーションに必要なセキュリティ機能を十分に備えています。

Rails 8 は「シンプルさ」と「規約」を重視し、デフォルトで安全な設定を提供。開発者が意識しなくてもセキュアなアプリケーションが構築できます。

Laravel 12 は「柔軟性」と「エコシステム」を重視し、様々なセキュリティ要件に対応可能な豊富なオプションを提供しています。

最終的な選択は、プロジェクトの要件、チームのスキルセット、そして既存のインフラストラクチャによって決まるでしょう。重要なのは、どちらのフレームワークを選んでも、そのセキュリティ機能を正しく理解し、適切に実装することです。

セキュリティは一度設定したら終わりではありません。定期的なアップデート、脆弱性スキャン、そしてセキュリティベストプラクティスの継続的な学習が、安全なWebアプリケーションを維持する鍵となります。

コメント

タイトルとURLをコピーしました