API Development

Building Secure and Efficient APIs with Laravel 12

Master Laravel 12 API development with advanced security, performance optimization, authentication, rate limiting, and best practices for building production-ready APIs.

Muhammad Waqas

Muhammad Waqas

CEO at CentoSquare

|
03-Nov-2025
10 min read
Building Secure and Efficient APIs with Laravel 12

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.


Article Tags

Laravel 12 API Security Laravel Sanctum API Performance Laravel Caching Rate Limiting API Authentication Laravel Best Practices RESTful API Laravel Validation Redis Cache API Monitoring Laravel Telescope Web Security Backend Development

About the Author

Muhammad Waqas

Muhammad Waqas

CEO at CentoSquare

Founder & CEO at CentoSquare | Creator of NeedLaravelSite | Helping Businesses Grow with Cutting-Edge Web, Mobile & Marketing Solutions | Building Innovative Products