Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Johna's avatar

Throttle after authentication

I have this rate limiter

RateLimiter::for('some-task', function ($request) {
	return Limit::perHour(10)->by($request->user()->id);
 });

I want to use it after a user is authenticated

Route::post('todo', [TaskController::class, 'task'])
            ->middleware(['auth', 'throttle:some-task']);

However, I get an error

attempt to read property id on null

I'm sure it's from the rate limiter since if I do

RateLimiter::for('some-task', function ($request) {
	//dd('test');
	return Limit::perHour(10)->by($request->user()->id);
	//dd('limiter passed');
 });

If I uncomment 'test' i get 'test'. However, if i uncomment 'limiter passed', I get the 'id' error

Does the throttle middleware run globally?

How do I throttle by user id?

0 likes
6 replies
tykus's avatar

You cannot rely on having an authenticated user at that point in the lifecycle, so you need a fallback. Usually, we fallback to IP address using the null-safe object operator ?-> to guard against that error you are experiencing, e.g.

reurn Limit::perHour(10)->by($request->user()?->id ?: $request->ip())

Whenever we have an authenticated user, that ID will be used, otherwise it will use their IP address for guests.

Johna's avatar

"You cannot rely on having an authenticated user at that point in the lifecycle"

I don't understand, since I'm doing ->middleware(['auth', 'throttle:some-task']);, shouldn't auth middleware run first?

if i do ->by($request->user()?->id ?: $request->ip()), does that mean it will always use the ip for a key?

tykus's avatar

Apologies, you are correct I didn't read the OP correctly. The auth middleware should be executed before the throttle middleware. You haven't cached your routes, have you?

Johna's avatar

I don't remember caching the routes. Also after clearing the routes I got other errors. I tried some GET endpoints, cleared routes again now it works.

Johna's avatar

Nevermind, im having the issue again!

After more research I found out :

Laravel’s global throttle logic does not run inside your controller middleware stack.Even if you add your throttle middleware after auth in your route, the rate limiter callback can still execute before the request is guaranteed to have an authenticated user bound to it.

Is this true? Is this by design, some sort of limitation or am I doing something wrong?

I found a workaround by making a custom throttle middleware:

public function handle(Request $request, Closure $next, int $attempts, int $minutes): Response
    {
        $key =  $request->user()->id;

        // Check attemps
        if (RateLimiter::tooManyAttempts($key, $attempts)) {
            return response()->json(
                ['message' => 'Too many requests'],
                Response::HTTP_TOO_MANY_REQUESTS,
                ['Retry-After' => RateLimiter::availableIn($key)]
            );
        }

        // Add hit
        RateLimiter::hit($key, $minutes * 60);
        return $next($request);
    }

Please or to participate in this conversation.