Building secure and efficient APIs is fundamental to modern web and mobile applications. Laravel 12 provides a comprehensive toolkit with enhanced features for creating robust APIs that handle high traffic securely while maintaining optimal performance. This guide covers essential security practices, performance optimization techniques, and industry best practices.
Why API Security and Performance Matter
API security breaches and performance bottlenecks can severely impact your application. A compromised API exposes sensitive user data, while slow APIs frustrate users and increase server costs. Laravel 12 addresses both concerns with built-in tools and elegant solutions.
Key considerations:
- Authentication and authorization – Verify user identity and permissions
- Data validation – Prevent malicious or malformed data
- Rate limiting – Protect against DDoS and abuse
- Performance optimization – Reduce response times and server load
- Error handling – Secure error messages without exposing sensitive info
- Monitoring and logging – Track issues and security threats
1. Implementing Robust Authentication with Laravel Sanctum
Laravel Sanctum provides a lightweight authentication system perfect for SPAs, mobile applications, and token-based APIs. It offers both API token authentication and SPA authentication using Laravel's session-based authentication.
Installation and Configuration
composer require laravel/sanctum
php artisan install:api
This command publishes Sanctum's configuration and runs necessary migrations.
Setting Up the User Model
Update your User model to use Sanctum's traits:
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
protected $fillable = [
'name',
'email',
'password',
];
protected $hidden = [
'password',
'remember_token',
];
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
}
Creating Authentication Endpoints
Build secure login and registration endpoints:
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
/**
* Register a new user.
*/
public function register(Request $request): JsonResponse
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => ['required', 'confirmed', Password::defaults()],
'device_name' => 'required|string',
]);
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
$token = $user->createToken($validated['device_name'])->plainTextToken;
return response()->json([
'user' => $user,
'token' => $token,
'token_type' => 'Bearer',
], 201);
}
/**
* Authenticate user and generate token.
*/
public function login(Request $request): JsonResponse
{
$request->validate([
'email' => 'required|email',
'password' => 'required',
'device_name' => 'required|string',
]);
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
// Revoke existing tokens for security
$user->tokens()->delete();
$token = $user->createToken($request->device_name)->plainTextToken;
return response()->json([
'user' => $user,
'token' => $token,
'token_type' => 'Bearer',
]);
}
/**
* Logout user and revoke token.
*/
public function logout(Request $request): JsonResponse
{
$request->user()->currentAccessToken()->delete();
return response()->json([
'message' => 'Logged out successfully',
]);
}
/**
* Get authenticated user profile.
*/
public function profile(Request $request): JsonResponse
{
return response()->json([
'user' => $request->user(),
]);
}
}
Protecting API Routes
Apply Sanctum middleware to protect your endpoints:
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\AuthController;
use App\Http\Controllers\Api\PostController;
// Public routes
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
// Protected routes
Route::middleware('auth:sanctum')->group(function () {
Route::get('/profile', [AuthController::class, 'profile']);
Route::post('/logout', [AuthController::class, 'logout']);
Route::apiResource('posts', PostController::class);
});
Token Abilities and Permissions
Sanctum supports token abilities for fine-grained permissions:
// Create token with specific abilities
$token = $user->createToken('api-token', ['post:create', 'post:update'])->plainTextToken;
// Check token abilities in controller
public function store(Request $request)
{
if (!$request->user()->tokenCan('post:create')) {
return response()->json(['message' => 'Forbidden'], 403);
}
// Create post logic
}
// Protect routes by ability
Route::middleware(['auth:sanctum', 'ability:post:create'])->post('/posts', [PostController::class, 'store']);
2. Advanced Request Validation
Robust validation prevents security vulnerabilities and ensures data integrity. Laravel 12 offers multiple validation approaches.
Form Request Classes
Create dedicated validation classes for complex validation logic:
php artisan make:request StorePostRequest
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class StorePostRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return $this->user()->can('create', Post::class);
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'title' => 'required|string|max:255|unique:posts',
'content' => 'required|string|min:100',
'category_id' => 'required|exists:categories,id',
'status' => ['required', Rule::in(['draft', 'published'])],
'tags' => 'array|max:5',
'tags.*' => 'string|max:50',
'published_at' => 'nullable|date|after:now',
'featured_image' => 'nullable|image|max:2048|dimensions:min_width=800,min_height=600',
];
}
/**
* Get custom messages for validator errors.
*/
public function messages(): array
{
return [
'title.unique' => 'A post with this title already exists.',
'content.min' => 'Content must be at least 100 characters.',
'featured_image.dimensions' => 'Image must be at least 800x600 pixels.',
];
}
/**
* Prepare data for validation.
*/
protected function prepareForValidation(): void
{
$this->merge([
'slug' => str($this->title)->slug(),
]);
}
}
Using Form Requests in Controllers
public function store(StorePostRequest $request)
{
$post = Post::create($request->validated());
return new PostResource($post);
}
Custom Validation Rules
Create reusable custom validation rules:
php artisan make:rule ValidDomain
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class ValidDomain implements ValidationRule
{
/**
* Run the validation rule.
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$allowedDomains = ['example.com', 'test.com'];
$domain = parse_url($value, PHP_URL_HOST);
if (!in_array($domain, $allowedDomains)) {
$fail('The :attribute must be from an allowed domain.');
}
}
}
Usage:
$request->validate([
'website' => ['required', 'url', new ValidDomain],
]);
3. Performance Optimization with Caching
Caching dramatically reduces database queries and improves API response times. Laravel 12 supports multiple cache drivers including Redis, Memcached, and DynamoDB.
Configuration
Update config/cache.php or use environment variables:
CACHE_STORE=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
Basic Caching Strategies
use Illuminate\Support\Facades\Cache;
// Store data for 1 hour
Cache::put('users', User::all(), 3600);
// Remember: retrieve or store
$users = Cache::remember('users', 3600, function () {
return User::with('profile')->get();
});
// Forever cache (until manually deleted)
Cache::forever('settings', $settings);
// Check existence
if (Cache::has('users')) {
$users = Cache::get('users');
}
// Delete cache
Cache::forget('users');
Query Result Caching
Cache expensive database queries:
namespace App\Repositories;
use App\Models\Post;
use Illuminate\Support\Facades\Cache;
class PostRepository
{
public function getAllPublished()
{
return Cache::remember('posts.published', 1800, function () {
return Post::with(['author', 'category', 'tags'])
->where('status', 'published')
->latest()
->get();
});
}
public function getById(int $id)
{
return Cache::remember("posts.{$id}", 3600, function () use ($id) {
return Post::with(['author', 'comments'])
->findOrFail($id);
});
}
public function clearCache(): void
{
Cache::forget('posts.published');
// Clear all post caches
Cache::tags(['posts'])->flush();
}
}
Cache Tags for Organized Cache Management
// Store with tags (Redis/Memcached only)
Cache::tags(['posts', 'featured'])->put('featured.posts', $posts, 3600);
// Retrieve tagged cache
$posts = Cache::tags(['posts', 'featured'])->get('featured.posts');
// Flush all caches with specific tag
Cache::tags(['posts'])->flush();
Response Caching
Cache entire HTTP responses:
use Illuminate\Support\Facades\Route;
Route::middleware('cache.headers:public;max_age=3600')->group(function () {
Route::get('/posts', [PostController::class, 'index']);
});
4. Implementing Rate Limiting
Rate limiting prevents API abuse, DDoS attacks, and ensures fair resource usage among users.
Basic Rate Limiting
Configure rate limits in bootstrap/app.php:
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
Apply to routes:
Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
Route::apiResource('posts', PostController::class);
});
Custom Rate Limiters
Create different limits for different endpoints:
// In bootstrap/app.php
RateLimiter::for('uploads', function (Request $request) {
return $request->user()?->isPremium()
? Limit::none()
: Limit::perMinute(10)->by($request->user()->id);
});
RateLimiter::for('search', function (Request $request) {
return Limit::perMinute(30)
->by($request->user()?->id ?: $request->ip())
->response(function (Request $request, array $headers) {
return response()->json([
'message' => 'Too many requests. Please try again later.',
], 429, $headers);
});
});
Apply custom limiters:
Route::middleware('throttle:uploads')->post('/upload', [UploadController::class, 'store']);
Route::middleware('throttle:search')->get('/search', [SearchController::class, 'index']);
Dynamic Rate Limiting
RateLimiter::for('dynamic', function (Request $request) {
$user = $request->user();
if ($user?->isAdmin()) {
return Limit::none();
}
if ($user?->isPremium()) {
return Limit::perMinute(120);
}
return Limit::perMinute(30);
});
5. Security Best Practices
CORS Configuration
Configure Cross-Origin Resource Sharing in config/cors.php:
return [
'paths' => ['api/*'],
'allowed_methods' => ['*'],
'allowed_origins' => [
'https://yourdomain.com',
'https://app.yourdomain.com',
],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];
SQL Injection Prevention
Always use Eloquent ORM or parameter binding:
// ✅ Safe - Eloquent ORM
$user = User::where('email', $request->email)->first();
// ✅ Safe - Query Builder with bindings
$user = DB::table('users')->where('email', $request->email)->first();
// ❌ Dangerous - Raw query without bindings
$user = DB::select("SELECT * FROM users WHERE email = '{$request->email}'");
Mass Assignment Protection
Define fillable or guarded properties:
class User extends Model
{
protected $fillable = ['name', 'email', 'password'];
// Or use guarded
protected $guarded = ['id', 'is_admin'];
}
Secure Error Handling
Hide sensitive errors in production:
// config/app.php
'debug' => env('APP_DEBUG', false),
// Custom exception handling in bootstrap/app.php
use Illuminate\Foundation\Configuration\Exceptions;
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (Throwable $e, Request $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => app()->environment('production')
? 'Server error occurred'
: $e->getMessage(),
], 500);
}
});
})
6. Logging and Monitoring
Setting Up Laravel Telescope
Install Telescope for debugging:
composer require laravel/telescope
php artisan telescope:install
php artisan migrate
Configure in config/telescope.php:
'enabled' => env('TELESCOPE_ENABLED', true),
'middleware' => ['web', 'auth'],
Custom Logging
use Illuminate\Support\Facades\Log;
// Different log levels
Log::emergency('System down');
Log::alert('Database connection lost');
Log::critical('Payment gateway failure');
Log::error('API request failed', ['endpoint' => $endpoint]);
Log::warning('High memory usage');
Log::notice('User logged in', ['user_id' => $user->id]);
Log::info('Post created', ['post_id' => $post->id]);
Log::debug('Variable value', ['data' => $data]);
// Contextual logging
Log::channel('api')->info('API request received', [
'endpoint' => $request->path(),
'method' => $request->method(),
'user_id' => auth()->id(),
]);
Integration with Monitoring Tools
Configure services like Sentry, Bugsnag, or Flare:
composer require sentry/sentry-laravel
php artisan sentry:publish --dsn=your-dsn-here
7. API Documentation
Use Laravel's OpenAPI tools or Scribe for automatic documentation:
composer require knuckleswtf/scribe
php artisan vendor:publish --tag=scribe-config
php artisan scribe:generate
Conclusion
Building secure and efficient APIs with Laravel 12 requires a multi-layered approach combining authentication, validation, caching, rate limiting, and monitoring. By implementing Sanctum for token-based authentication, robust validation with Form Requests, strategic caching with Redis, intelligent rate limiting, and comprehensive logging, you create APIs that are both secure and performant.
Remember to regularly update dependencies, conduct security audits, monitor API performance metrics, and follow Laravel's security best practices. These practices ensure your API remains reliable, secure, and scalable as your application grows.