Mastering Application Security: The Power of Rate Limiting

bensonmacharia

Benson Macharia

Posted on March 15, 2024

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

Enter fullscreen mode Exit fullscreen mode

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}}

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](https://portswigger.net/burp/communitydownload) repeater or intruder as illustrated in the image below.

![Error Too May Requests API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c3nq36qvh7b11oragahb.png)
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
Enter fullscreen mode Exit fullscreen mode

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

- List all users
Enter fullscreen mode Exit fullscreen mode

$ curl -X GET --header "Content-Type: application/json" --header "Authorization: Bearer z7ZOw6IqUbDHLdozERIwp4w9Ti8wfvaI51b8xTqp3Vc4w4JDCYAoqTbunQ12" http://localhost:8000/api/users

- Get user by ID
Enter fullscreen mode Exit fullscreen mode

$ curl -X GET --header "Content-Type: application/json" --header "Authorization: Bearer z7ZOw6IqUbDHLdozERIwp4w9Ti8wfvaI51b8xTqp3Vc4w4JDCYAoqTbunQ12" http://localhost:8000/api/user/1

- Get user profile
Enter fullscreen mode Exit fullscreen mode

$ curl -X GET --header "Content-Type: application/json" --header "Authorization: Bearer z7ZOw6IqUbDHLdozERIwp4w9Ti8wfvaI51b8xTqp3Vc4w4JDCYAoqTbunQ12" http://localhost:8000/api/profile

- Add product
Enter fullscreen mode Exit fullscreen mode

$ 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

- Get all products
Enter fullscreen mode Exit fullscreen mode

$ curl -X GET --header "Content-Type: application/json" --header "Authorization: Bearer z7ZOw6IqUbDHLdozERIwp4w9Ti8wfvaI51b8xTqp3Vc4w4JDCYAoqTbunQ12" http://localhost:8000/api/products

- Get product by ID
Enter fullscreen mode Exit fullscreen mode

$ 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"}

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/92g91146zuno2kjjxv4r.png)

- Throttled product requests
![Throttled product requests](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u0mroaipt3wys88acn2j.png)

- User credentials bruteforce attack prevented on login page

![User login page bruteforce blocked](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lyh22gf8ekrdwdvj15gx.png)

**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**
- Connect on [LinkedIn](https://www.linkedin.com/in/benson-macharia)
- Check out my [GitHub](https://github.com/bensonmacharia)
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
bensonmacharia
Benson Macharia

Posted on March 15, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related