DEV Community

Cover image for Mastering Application Security: The Power of Rate Limiting
Benson Macharia
Benson Macharia

Posted on

Mastering Application Security: The Power of Rate Limiting

What is Rate Limiting?
Rate limiting is a technique used in web and API development to control the rate of traffic sent or received by a server.

Why Rate Limiting?
Rate limiting is a crucial aspect of modern application security, offering a powerful defense against various types of attacks such as brute force, DDoS, and API abuse. By restricting the number of requests a user or IP address can make within a certain timeframe, rate limiting helps prevent server overload, maintains application performance, and enhances overall security posture.

Implementation
In this article, we'll delve into the concept of rate limiting in Laravel; a popular PHP framework. We will explore how to set it up, customize it to suit your application's needs, and handle common scenarios. By the end, you'll have the knowledge and confidence to implement rate limiting in your Laravel applications, enhancing their security and stability.

Design

Laravel Design
In Laravel, requests are routed through the router service, which directs them to the appropriate middleware for processing, then to controllers for handling business logic, models for interacting with the database, and finally to views for rendering the response back to the client.

Let's Set Up
You can download the complete source code here or you can follow this Laravel guide to scaffold your application as follows.

$ composer create-project laravel/laravel:^10.0 laravel-rate-limiting
$ cd laravel-rate-limiting
$ php artisan serve
Enter fullscreen mode Exit fullscreen mode

Remember to edit your .env file and then run

$ php artisan migrate 
Enter fullscreen mode Exit fullscreen mode

We will additionally create:-

  • Three controllers i.e. LoginRegisterController.php, APIController.php and ProductController.php under app/Http/Controllers
  • Two Models i.e. User.php and Product.php under app/Models.
  • Auth and Product views under resources/views
  • Migrations under database/migrations

Rate Limiting Implementation
To implement rate limiting, we will make use of the rate limiting capability provided with Laravel Middleware to throttle incoming HTTP requests before they reach our controllers.
To do that, we will modify the App\Providers\RouteServiceProvider.php to define the rate limiter configurations to suit our application needs.

<?php
namespace App\Providers;
class RouteServiceProvider extends ServiceProvider
{
public function boot(): void
    {
        // Modify rate limiting for APIs to 60 requests per minute
        RateLimiter::for('api', function (Request $request) {
            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
        });

        // Rate limit auth endpoints to 10 requests per minute by user IP
        RateLimiter::for('auth', function (Request $request) {
            return Limit::perMinute(10)->by($request->ip());
        });

        // Rate limit products endpoints to 30 requests per minute by user ID
        RateLimiter::for('product', function (Request $request) {
            return Limit::perMinute(30)->by($request->user()?->id);
        });

        $this->routes(function () {
            Route::middleware('api')
                ->prefix('api')
                ->group(base_path('routes/api.php'));

            Route::middleware('web')
                ->group(base_path('routes/web.php'));
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

In the above example, we have defined two rate limiters, one called auth that limits access to the auth routes to 10 requests per minute by user IP and another one called product that limits access to products endpoints to 30 requests per minute by user ID. We have also modified the rate limiter for APIs to 60 requests per minute by user IP or ID.
We can now apply the rate limiters to the routes we want to throttle using the middleware as follows:

routes/api.php

// Endpoint requires authentication and is throttled through the auth rate limiter
Route::middleware(['auth:api','throttle:auth'])->get('/profile', function (Request $request) {
    return $request->user();
});

// Endpoints do not require authentication but they are throttled through the auth rate limiter
Route::middleware(['throttle:auth'])->post('/user/register', [APIController::class, 'register']);
Route::middleware(['throttle:auth'])->post('/user/login', [APIController::class, 'login']);

// Endpoints require authentication and are throttled through the auth rate limiter
Route::middleware(['auth:api','throttle:auth'])->get('/users', [APIController::class, 'users']);
Route::middleware(['auth:api','throttle:auth'])->get('/user/{id}', [APIController::class, 'user']);

// Endpoints require authentication and are throttled through the auth product limiter
Route::middleware(['auth:api','throttle:product'])->post('/product', [APIController::class, 'newProduct']);
Route::middleware(['auth:api','throttle:product'])->get('/products', [APIController::class, 'index']);
Route::middleware(['auth:api','throttle:product'])->get('/product/{id}', [APIController::class, 'product']);
Enter fullscreen mode Exit fullscreen mode

routes/web.php

// Not throttled
Route::get('/', function () {
    return view('welcome');
});

// Throttled through the auth rate limiter
Route::group(['middleware' => 'throttle:auth'], function () {
    Route::get('/register', [LoginRegisterController::class, 'register'])->name('register');
    Route::post('/store', [LoginRegisterController::class, 'store'])->name('store');
    Route::get('/login', [LoginRegisterController::class, 'login'])->name('login');
    Route::post('/authenticate', [LoginRegisterController::class, 'authenticate'])->name('authenticate');
    Route::get('/dashboard', [LoginRegisterController::class, 'dashboard'])->name('dashboard');
    Route::post('/logout', [LoginRegisterController::class, 'logout'])->name('logout');
});

// Throttled through the product rate limiter
Route::middleware(['middleware' => 'throttle:product'])->group(function () {
    Route::get('/products', [ProductController::class, 'index'])->name('products');
    Route::get('/product/{product}', [ProductController::class, 'show'])->name('product.show');
    Route::post('/product', [ProductController::class, 'store'])->name('product.store');
});
Enter fullscreen mode Exit fullscreen mode

Testing Rate Limiting
Moment on truth.!
Run

$ php artisan serve

1. API
We have eight API endpoints as below

  • User register
$ curl -X POST --header "Content-Type: application/json" --data '{"name":"Jane Doe", "email":"janedoe@example.com", "password":"pAssW0rd@s3cr3t", "password_confirmation":"pAssW0rd@s3cr3t"}' http://localhost:8000/api/user/register

HTTP/1.1 201 Created
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 9
{"message":"User registered successfully","user":{"name":"John Doe","email":"johndoe@example.com","api_token":"hSAx1XBcpFDNUNFqCQvQ9OOgGfBF6t5rzxJicEsjsh0o4HGmaBCVhA7tOEpp","updated_at":"2024-03-14T23:30:45.000000Z","created_at":"2024-03-14T23:30:45.000000Z","id":9}} 
Enter fullscreen mode Exit fullscreen mode

On successful user registration we can see the response above. Note the X-RateLimit-Limit and X-RateLimit-Remaining values meaning we have nine more requests remaining allowed before we get blocked. We can simulate this using a tool such as Burp suite repeater or intruder as illustrated in the image below.

Error Too May Requests API
After sending 10 requests, we will be blocked on the 11th one as per the configuration we set in the API auth limiter.

Try the other endpoints as below.

  • User login
$ curl -X POST --header "Content-Type: application/json" --data '{"email":"janedoe@example.com", "password":"pAssW0rd@s3cr3t"}' http://localhost:8000/api/user/login
Enter fullscreen mode Exit fullscreen mode
  • List all users
$ curl -X GET --header "Content-Type: application/json" --header "Authorization: Bearer z7ZOw6IqUbDHLdozERIwp4w9Ti8wfvaI51b8xTqp3Vc4w4JDCYAoqTbunQ12" http://localhost:8000/api/users
Enter fullscreen mode Exit fullscreen mode
  • Get user by ID
$ curl -X GET --header "Content-Type: application/json" --header "Authorization: Bearer z7ZOw6IqUbDHLdozERIwp4w9Ti8wfvaI51b8xTqp3Vc4w4JDCYAoqTbunQ12" http://localhost:8000/api/user/1
Enter fullscreen mode Exit fullscreen mode
  • Get user profile
$ curl -X GET --header "Content-Type: application/json" --header "Authorization: Bearer z7ZOw6IqUbDHLdozERIwp4w9Ti8wfvaI51b8xTqp3Vc4w4JDCYAoqTbunQ12" http://localhost:8000/api/profile
Enter fullscreen mode Exit fullscreen mode
  • Add product
$ curl -X POST --header "Content-Type: application/json" --header "Authorization: Bearer z7ZOw6IqUbDHLdozERIwp4w9Ti8wfvaI51b8xTqp3Vc4w4JDCYAoqTbunQ12" --data '{"name":"Nike Air Force 1 Shadow", "quantity":300, "price":4500.00}' http://localhost:8000/api/product
Enter fullscreen mode Exit fullscreen mode
  • Get all products
$ curl -X GET --header "Content-Type: application/json" --header "Authorization: Bearer z7ZOw6IqUbDHLdozERIwp4w9Ti8wfvaI51b8xTqp3Vc4w4JDCYAoqTbunQ12" http://localhost:8000/api/products
Enter fullscreen mode Exit fullscreen mode
  • Get product by ID
$ curl -X GET --header "Content-Type: application/json" --header "Authorization: Bearer z7ZOw6IqUbDHLdozERIwp4w9Ti8wfvaI51b8xTqp3Vc4w4JDCYAoqTbunQ12" http://localhost:8000/api/product/1

HTTP/1.1 200 OK
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 29
{"id":1,"name":"Product One","quantity":23,"price":400.5,"created_at":"2024-03-14T15:59:26.000000Z","updated_at":"2024-03-14T15:59:26.000000Z"}
Enter fullscreen mode Exit fullscreen mode

For the products endpoints we will be throttled at 30 requests per minute as per our API product limiter.

2. Web
On the web, all our authentication related pages will be rate limited at 10 requests per minute as per our auth limiter configuration while those pages concerning products will be throttled at 30 requests per minute.

  • Throttled authentication requests
    Throttled auth requests

  • Throttled product requests
    Throttled product requests

  • User credentials bruteforce attack prevented on login page

User login page bruteforce blocked

Finally
As we have seen, rate limiting in Laravel is a powerful tool for controlling the flow of requests to your application, ensuring its stability and security. By following the guidelines outlined in this article, you can effectively implement rate limiting to protect your application from abuse and ensure a smooth user experience.

Let's connect

Top comments (0)