Security
This guide covers essential security practices when working with Laravel DataTables. Understanding these patterns helps protect your application from common vulnerabilities.
[!NOTE] Laravel DataTables provides secure defaults. Most security features work automatically, but you need to be intentional when customizing data output.
Overview
DataTables security involves several key areas:
| Area | Risk | Default Protection |
|---|---|---|
| XSS | Malicious scripts in user data | Automatic escaping |
| CSRF | Cross-site request forgery | Token handling via scripts() |
| Mass Assignment | Unintended data modification | $fillable/$guarded attributes |
| Authorization | Unauthorized data access | Manual policy checks |
XSS Protection
How It Works
Laravel DataTables escapes all column data by default, converting special characters to HTML entities. This prevents malicious scripts from executing in the browser.
// Input: <script>alert('hacked')</script>// Output (escaped): <script>alert('hacked')</script>
Allowing HTML in Specific Columns
When you need to render HTML (buttons, badges, links), use rawColumns to mark trusted columns:
use Yajra\DataTables\Facades\DataTables;use App\Models\User; Route::get('user-data', function() { return DataTables::eloquent(User::query()) ->addColumn('action', function (User $user) { // Generate HTML for action buttons return view('users.datatable-actions', ['user' => $user])->render(); }) ->rawColumns(['action']) // Mark as safe - contains no user input ->toJson();});
[!WARNING] Never pass unsanitized user input to rawColumns. The
rawColumnsmethod trusts the content completely. If user data must be displayed as HTML, sanitize it first using a library like HTML Purifier.
Safe vs Unsafe Patterns
// ✅ SAFE - Static HTML with no user data->addColumn('status_badge', '<span class="badge bg-success">Active</span>')->rawColumns(['status_badge']) // ✅ SAFE - User data is escaped->addColumn('name', e($user->name)) // ⚠️ DANGEROUS - User input in raw column->addColumn('bio', '<p>' . $user->bio . '</p>')->rawColumns(['bio']) // User's bio could contain malicious scripts! // ✅ SAFE - Sanitized user data in raw columnuse HTMLPurifier;->addColumn('bio', '<p>' . (new HTMLPurifier())->purify($user->bio) . '</p>')->rawColumns(['bio'])
CSRF Protection
The Problem
Without CSRF protection, malicious websites could trick users into submitting requests to your DataTables endpoint while they're authenticated.
Attacker Site → Hidden Form → Your DataTables Endpoint (with user's session)
Automatic Protection with scripts()
Laravel DataTables handles CSRF automatically when you include the JavaScript properly:
Step 1: Include scripts in your Blade template
@push('scripts') {{ $dataTable->scripts() }}@endpush
Step 2: Ensure CSRF meta tag exists (Laravel default)
<head> <meta name="csrf-token" content="{{ csrf_token() }}"></head>
Step 3: Ensure app.blade.php includes the CSRF middleware (default in Laravel)
// App\Http\Middleware\VerifyCsrfToken.phpprotected $except = [ // Add exceptions only for endpoints that DON'T need CSRF // DataTables endpoints should NOT be here];
[!IMPORTANT] The
scripts()method automatically reads the CSRF token and includes it with all AJAX requests. Do not disable this protection.
What Happens Without scripts()?
If you implement DataTables JavaScript manually without using scripts():
// ⚠️ Manual AJAX without CSRF - VULNERABLE$.ajax({ url: '/data/users', type: 'GET' // CSRF token NOT included!});
You must manually add the token to each request:
// ✅ Manual implementation WITH CSRF$.ajaxSetup({ headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content }});
Mass Assignment Protection
The Problem
Without protection, attackers could modify fields they shouldn't access:
// ⚠️ VULNERABLE - User controls all inputUser::create($request->all()); // Could include 'is_admin' => true
Using $fillable
Define which fields can be mass-assigned:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * Fields that can be set via mass assignment. * Only include fields users should be able to modify. */ protected $fillable = [ 'name', 'email', 'password', ]; /** * Fields that are NEVER mass-assignable. * Security-sensitive fields should ALWAYS be here. */ protected $guarded = [ 'id', 'is_admin', 'role', 'created_at', 'updated_at', ];}
With DataTables Editor
When using the Editor plugin for inline editing:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Yajra\DataTables\Editor\Eloquent;use Yajra\DataTables\Editor\Fields; class User extends Model{ protected $fillable = [ 'name', 'email', 'department_id', ]; // Editor integration public static function editor() { return Eloquent::model(new self) ->field(Field::text('name')->validator('required|max:255')) ->field(Field::text('email')->validator('required|email')) ->field(Field::text('department_id')); // User can change department // Note: 'is_admin' is intentionally omitted - cannot be changed via editor }}
[!WARNING] Never include security-sensitive fields in
$fillable. Fields likeis_admin,role,password(when accepting plain text), andcreated_atshould always be guarded.
Authorization & Access Control
Why You Need It
DataTables queries can return sensitive data. Always verify the user has permission to view that data.
Policy-Based Authorization
use App\Models\User;use App\Policies\UserPolicy; Route::get('user-data', function() { $query = User::query(); // Apply policy-based filtering if (!auth()->user()->can('viewAny', User::class)) { // Return only the current user's data $query->where('id', auth()->id()); } return DataTables::eloquent($query) ->toJson();});
Column-Level Authorization
Hide sensitive columns based on permissions:
use Yajra\DataTables\Facades\DataTables; Route::get('user-data', function() { $datatable = DataTables::eloquent(User::query()); // Only show salary to managers if (auth()->user()->isManager()) { $datatable->addColumn('salary', fn(User $user) => $user->salary); } else { $datatable->editColumn('salary', fn(User $user) => '****'); // Masked } return $datatable->toJson();});
Route Protection
Ensure endpoints are properly guarded:
// ✅ Protected routesRoute::middleware(['auth', 'verified'])->group(function () { Route::get('user-data', [UserController::class, 'data']); Route::post('user-data/store', [UserController::class, 'store']);}); // ⚠️ Public endpoints are dangerousRoute::get('user-data', [UserController::class, 'data']); // No auth!
API Security Considerations
Authentication for API Endpoints
When DataTables serves an API (SPA/mobile):
Route::middleware(['auth:sanctum'])->group(function () { Route::get('user-data', [UserController::class, 'data']);});
Rate Limiting
Protect against abuse:
use Illuminate\Cache\RateLimiting\Limit;use Illuminate\Support\Facades\RateLimiter; RateLimiter::for('datatables', function (Request $request) { return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());}); Route::middleware(['auth:sanctum', 'throttle:datatables'])->group(function () { Route::get('user-data', [UserController::class, 'data']);});
Security Checklist
Use this checklist when implementing DataTables:
- XSS: Only use
rawColumnswith trusted, sanitized content - XSS: Use
e()helper when displaying user input - CSRF: Include
scripts()method or manually add CSRF token to AJAX - Mass Assignment: Define
$fillablewith only necessary fields - Mass Assignment: Keep sensitive fields in
$guarded - Authorization: Add policy checks before returning data
- Authorization: Hide sensitive columns based on permissions
- API: Protect endpoints with authentication middleware
- API: Consider rate limiting for expensive queries
See Also
- XSS Filtering - Detailed XSS protection guide
- Raw Columns - Using
rawColumnssafely - Editor Installation - Secure editor setup
Security Contact
If you discover a security vulnerability, please email [email protected] directly. Do not report security issues through the public issue tracker.