※本記事は「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アプリケーションを維持する鍵となります。
コメント