@nakov Only issue with using limiter as middleware for the logins means failed or not, it counts towards the limit, where you may only want to increment attempts on fail.
@jvbalcita So what I ended up doing was basically copying what the base login controller (OG/UI) did using the rate limiter into a trait to use in my api login (though I use passport).
Trait
<?php
namespace App\Http\Controllers\Auth\Concerns;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Cache\RateLimiter;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
trait ThrottlesAttempts
{
/**
* Determine if the user has too many failed login attempts.
*
* @param Request $request
* @return bool
*/
protected function hasTooManyAttempts(Request $request): bool
{
return $this->limiter()->tooManyAttempts(
$this->throttleKey($request), $this->maxAttempts()
);
}
/**
* Increment the login attempts for the user.
*
* @param Request $request
* @return void
*/
protected function incrementAttempts(Request $request): void
{
$this->limiter()->hit(
$this->throttleKey($request), $this->decayMinutes() * 60
);
}
/**
* Redirect the user after determining they are locked out.
*
* @param Request $request
* @return void
* @throws ValidationException
*/
protected function sendLockoutResponse(Request $request): void
{
$seconds = $this->limiter()->availableIn(
$this->throttleKey($request)
);
throw ValidationException::withMessages([
$this->throttleKeyName() => [Lang::get('auth.throttle', [
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
])],
])->status(Response::HTTP_TOO_MANY_REQUESTS);
}
/**
* Clear the login locks for the given user credentials.
*
* @param Request $request
* @return void
*/
protected function clearAttempts(Request $request): void
{
$this->limiter()->clear($this->throttleKey($request));
}
/**
* Fire an event when a lockout occurs.
*
* @param Request $request
* @return void
*/
protected function fireLockoutEvent(Request $request): void
{
event(new Lockout($request));
}
/**
* Get the throttle key for the given request.
*
* @param Request $request
* @return string
*/
protected function throttleKey(Request $request): string
{
return Str::lower($request->input($this->throttleKeyName())).'|'.$request->ip();
}
/**
* Get the rate limiter instance.
*
* @return RateLimiter
*/
protected function limiter(): RateLimiter
{
return app(RateLimiter::class);
}
/**
* Get the maximum number of attempts to allow.
*
* @return int
*/
public function maxAttempts(): int
{
return property_exists($this, 'maxAttempts')
? $this->maxAttempts
: 5;
}
/**
* Get the number of minutes to throttle for.
*
* @return int
*/
public function decayMinutes(): int
{
return property_exists($this, 'decayMinutes')
? $this->decayMinutes
: 1;
}
/**
* Get the key used to throttle request
*
* @return string
*/
public function throttleKeyName(): string
{
return property_exists($this, 'throttleKeyName')
? $this->throttleKeyName
: 'email';
}
}
Controller
use ThrottlesAttempts;
class ApiLogin extends Controller
{
/**
* Handle a login request to the application.
*/
public function login(Request $request)
{
if($this->hasTooManyAttempts($request))
{
return $this->sendLockoutResponse($request);
}
if (!Auth::attempt($credentials)) {
$this->incrementAttempts($request);
return response()->json([
'status_code' => 401,
'message' => 'Unauthorized'
]);
}
//auth attempt good
$this->clearAttempts($request);
//w.e else you do
}