Laravel 9 password.confirm middleware feature not working - php

I am trying to use the password.confirm middleware in some of my routes which I need to be secured. I want a user should confirm his password when he generate a specific post request.
Also, password.confirm middleware is added $routeMiddleware property in the app/Http/Kernel.php file.
This is what my route looks like in web.php.
Route::post('fetchResult', [ReportController::class, 'fetchResult'])->name('fetchComplaintResult')->middleware('password.confirm');
But it is not working. When I generate post request it directly hit to controller fetchResult method as usual.
My controller function:
public function fetchResult(Request $request)
{
Model::create([
'id' => $request->id,
'action' => $request->action,
'comments' => $request->comment
]);
// Other Action
}
Am I missing something ??

If you want to get prompted to confirm your password every time you should write your route like this:
Route::post('fetchResult', [ReportController::class, 'fetchResult'])
->name('fetchComplaintResult')
->middleware('password.confirm:password.confirm,1');
Why?
The password.confirm middleware maps to the Illuminate\Auth\Middleware\RequirePassword class. This is its code.
use Closure;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Contracts\Routing\UrlGenerator;
class RequirePassword
{
/**
* The response factory instance.
*
* #var \Illuminate\Contracts\Routing\ResponseFactory
*/
protected $responseFactory;
/**
* The URL generator instance.
*
* #var \Illuminate\Contracts\Routing\UrlGenerator
*/
protected $urlGenerator;
/**
* The password timeout.
*
* #var int
*/
protected $passwordTimeout;
/**
* Create a new middleware instance.
*
* #param \Illuminate\Contracts\Routing\ResponseFactory $responseFactory
* #param \Illuminate\Contracts\Routing\UrlGenerator $urlGenerator
* #param int|null $passwordTimeout
* #return void
*/
public function __construct(ResponseFactory $responseFactory, UrlGenerator $urlGenerator, $passwordTimeout = null)
{
$this->responseFactory = $responseFactory;
$this->urlGenerator = $urlGenerator;
$this->passwordTimeout = $passwordTimeout ?: 10800;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string|null $redirectToRoute
* #param int|null $passwordTimeoutSeconds
* #return mixed
*/
public function handle($request, Closure $next, $redirectToRoute = null, $passwordTimeoutSeconds = null)
{
if ($this->shouldConfirmPassword($request, $passwordTimeoutSeconds)) {
if ($request->expectsJson()) {
return $this->responseFactory->json([
'message' => 'Password confirmation required.',
], 423);
}
return $this->responseFactory->redirectGuest(
$this->urlGenerator->route($redirectToRoute ?? 'password.confirm')
);
}
return $next($request);
}
/**
* Determine if the confirmation timeout has expired.
*
* #param \Illuminate\Http\Request $request
* #param int|null $passwordTimeoutSeconds
* #return bool
*/
protected function shouldConfirmPassword($request, $passwordTimeoutSeconds = null)
{
$confirmedAt = time() - $request->session()->get('auth.password_confirmed_at', 0);
return $confirmedAt > ($passwordTimeoutSeconds ?? $this->passwordTimeout);
}
}
The important parts to note are the handle method's signature:
public function handle($request, Closure $next, $redirectToRoute = null, $passwordTimeoutSeconds = null)
Here, $redirectToRoute and $passwordTimeoutSeconds are arguments we can set in the route file.
$redirectToRoute is used like this in the handle method:
$this->urlGenerator->route($redirectToRoute ?? 'password.confirm')
$passwordTimeoutSeconds is used here in the shouldConfirmPassword method.
return $confirmedAt > ($passwordTimeoutSeconds ?? $this->passwordTimeout);
Since it's set to null, the value $confirmedAt is compared to is $this->passwordTimeout, which is set in the constructor to 10800. (10800 seconds = 180 minutes = 3 hours).
So in summary we need to set the $passwordTimeoutSeconds parameter. Since it's the second parameter, we also need to set the $redirectToRoute parameter.
https://laravel.com/docs/9.x/middleware#middleware-parameters

Related

how to check santcum middleware in middleware class in laravel

i have two type of api authentication way and i want if my first way fails check santcum laravel auth .
i make midlleware class and but i dont know how to check santcum in that
<?php
namespace App\Http\Middleware;
class UserApiAuthenticated
{
/**
* User authenticator container
* #var UserAuthenticatorServiceInterface
*/
protected UserAuthenticatorServiceInterface $user_authenticator_service;
public function __construct(UserAuthenticatorServiceInterface$user_authenticator_service)
{
$this->user_authenticator_service = $user_authenticator_service;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure(\Illuminate\Http\Request):
(\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* #return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
if ($this->user_authenticator_service->isUserAuthenticated($request)) {
return $next($request);
} elseif () {
}
I would do something like that :
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* #param string|null ...$guards
*
* #return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next, ...$guards)
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard)
{
if (Auth::guard($guard)->check())
{
return $next($request);
}
}
abort(403, 'Unauthenticated')
}
it checks all the guards one by one,
when you use the middleware you set the guards :
->middleware('mymiddleware:web,sanctum')
or with a route group :
Route::group(['middleware' => ['mymiddleware:web,sanctum',
if you have routes in common, you could use both guards at the same time, and for routes that are specific to one authentication method, you can set the correct guard you need
but in the end i would check the auth middleware from laravel, i'm sure it does what you need (Illuminate\Auth\Middleware\Authenticate)

Laravel 8 Class 'Laravel\Fortify\Actions\Auth' not found

I'm trying to make an e-commerce admin/user authentication I use laravel 8 went register a test account and logged in and this error occured.
Error
Class 'Laravel\Fortify\Actions\Auth' not found
After i logged in a test account it was supposed to result like this
https://ibb.co/Vq5LxBk
C:\Users\ACER\laravel8ecommerce\vendor\laravel\fortify\src\Actions\AttemptToAuthenticate.php:58
This was the line 58
if(Auth::user()->utype === 'ADM')
My code on AttemptToAuthenticate.php
<?php
namespace Laravel\Fortify\Actions;
use Illuminate\Auth\Events\Failed;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Validation\ValidationException;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\LoginRateLimiter;
class AttemptToAuthenticate
{
/**
* The guard implementation.
*
* #var \Illuminate\Contracts\Auth\StatefulGuard
*/
protected $guard;
/**
* The login rate limiter instance.
*
* #var \Laravel\Fortify\LoginRateLimiter
*/
protected $limiter;
/**
* Create a new controller instance.
*
* #param \Illuminate\Contracts\Auth\StatefulGuard $guard
* #param \Laravel\Fortify\LoginRateLimiter $limiter
* #return void
*/
public function __construct(StatefulGuard $guard, LoginRateLimiter $limiter)
{
$this->guard = $guard;
$this->limiter = $limiter;
}
/**
* Handle the incoming request.
*
* #param \Illuminate\Http\Request $request
* #param callable $next
* #return mixed
*/
public function handle($request, $next)
{
if (Fortify::$authenticateUsingCallback) {
return $this->handleUsingCustomCallback($request, $next);
}
if ($this->guard->attempt(
$request->only(Fortify::username(), 'password'),
$request->filled('remember'))
) {
if(Auth::user()->utype === 'ADM')
{
session(['utype'=>'ADM']);
return redirect(RouteServiceProvider::HOME);
}
elseif(Auth::user()->utype === 'USR')
{
session(['utype'=>'USR']);
return redirect(RouteServiceProvider::HOME);
}
return $next($request);
}
$this->throwFailedAuthenticationException($request);
}
/**
* Attempt to authenticate using a custom callback.
*
* #param \Illuminate\Http\Request $request
* #param callable $next
* #return mixed
*/
protected function handleUsingCustomCallback($request, $next)
{
$user = call_user_func(Fortify::$authenticateUsingCallback, $request);
if (! $user) {
$this->fireFailedEvent($request);
return $this->throwFailedAuthenticationException($request);
}
$this->guard->login($user, $request->filled('remember'));
return $next($request);
}
/**
* Throw a failed authentication validation exception.
*
* #param \Illuminate\Http\Request $request
* #return void
*
* #throws \Illuminate\Validation\ValidationException
*/
protected function throwFailedAuthenticationException($request)
{
$this->limiter->increment($request);
throw ValidationException::withMessages([
Fortify::username() => [trans('auth.failed')],
]);
}
/**
* Fire the failed authentication attempt event with the given arguments.
*
* #param \Illuminate\Http\Request $request
* #return void
*/
protected function fireFailedEvent($request)
{
event(new Failed(config('fortify.guard'), null, [
Fortify::username() => $request->{Fortify::username()},
'password' => $request->password,
]));
}
My Routes (web php)
<?php
use App\Http\Livewire\CartComponent;
use App\Http\Livewire\CheckoutComponent;
use App\Http\Livewire\HomeComponent;
use App\Http\Livewire\ShopComponent;
use App\Http\Livewire\User\UserDashboardComponent;
use App\Http\Livewire\Admin\AdminDashboardComponent;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
// Route::get('/', function () {
// return view('welcome');
// });
Route::get('/',HomeComponent::class);
Route::get('/shop',ShopComponent::class);
Route::get('/cart',CartComponent::class);
Route::get('/checkout',CheckoutComponent::class);
// Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
// return view('dashboard');
// })->name('dashboard');
// For User or Customer
Route::middleware(['auth:sanctum', 'verified'])->group(function(){
Route::get('user/dashboard',UserDashboardComponent::class,)->name('user.dashboard');
});
// For Admin
Route::middleware(['auth:sanctum', 'verified','authadmin'])->group(function(){
Route::get('admin/dashboard',AdminDashboardComponent::class,)->name('admin.dashboard');
});
I would really appreciate a help i'm doing this for my project on school
use Illuminate\Support\Facades\Auth;
use App\Providers\RouteServiceProvider;
from to vendor\laravel\fortify\src\Actions\AttemptToAuthenticate
but you must remove line :
use Laravel\Fortify\Actions\RouteServiceProvider;
Because this line is duplicate for RouteServiceProvider.... but maybe just for my project
use Illuminate\Support\Facades\Auth;
use App\Providers\RouteServiceProvider;
put these two on top of
vendor\laravel\fortify\src\Actions\AttemptToAuthenticate
adding two lines on the top of AttemptToAuthenticate.php file
use Illuminate\Support\Facades\Auth;
use App\Providers\RouteServiceProvider;
For future users;
On AttemptToAuthenticate.php, right under AttemptToAuthenticate.php,
add use Auth;
it will still throw an error class '\laravel\fortify\actions\RouteServiceProvider' not found!
To solve this,
Simply add
use App\Providers\RouteServiceProvider;, to direct it to the default
RouteServiceProvider class.
add
use Illuminate\Support\Facades\Auth;
in class vendor\laravel\fortify\src\Actions\AttemptToAuthenticate
<?php
namespace Laravel\Fortify\Actions;
use Illuminate\Auth\Events\Failed;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Validation\ValidationException;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\LoginRateLimiter;
use Illuminate\Support\Facades\Auth;
use App\Providers\RouteServiceProvider;
class AttemptToAuthenticate
{
/**
* The guard implementation.
*
* #var \Illuminate\Contracts\Auth\StatefulGuard
*/
protected $guard;
/**
* The login rate limiter instance.
*
* #var \Laravel\Fortify\LoginRateLimiter
*/
protected $limiter;
/**
* Create a new controller instance.
*
* #param \Illuminate\Contracts\Auth\StatefulGuard $guard
* #param \Laravel\Fortify\LoginRateLimiter $limiter
* #return void
*/
public function __construct(StatefulGuard $guard, LoginRateLimiter $limiter)
{
$this->guard = $guard;
$this->limiter = $limiter;
}
/**
* Handle the incoming request.
*
* #param \Illuminate\Http\Request $request
* #param callable $next
* #return mixed
*/
public function handle($request, $next)
{
if (Fortify::$authenticateUsingCallback) {
return $this->handleUsingCustomCallback($request, $next);
}
if ($this->guard->attempt(
$request->only(Fortify::username(), 'password'),
$request->filled('remember'))
) {
if(Auth::user()->utype === 'ADM')
{
session(['utype'=>'ADM']);
return redirect(RouteServiceProvider::HOME);
}
elseif(Auth::user()->utype === 'USR')
{
session(['utype'=>'USR']);
return redirect(RouteServiceProvider::HOME);
}
return $next($request);
}
$this->throwFailedAuthenticationException($request);
}
/**
* Attempt to authenticate using a custom callback.
*
* #param \Illuminate\Http\Request $request
* #param callable $next
* #return mixed
*/
protected function handleUsingCustomCallback($request, $next)
{
$user = call_user_func(Fortify::$authenticateUsingCallback, $request);
if (! $user) {
$this->fireFailedEvent($request);
return $this->throwFailedAuthenticationException($request);
}
$this->guard->login($user, $request->filled('remember'));
return $next($request);
}
/**
* Throw a failed authentication validation exception.
*
* #param \Illuminate\Http\Request $request
* #return void
*
* #throws \Illuminate\Validation\ValidationException
*/
protected function throwFailedAuthenticationException($request)
{
$this->limiter->increment($request);
throw ValidationException::withMessages([
Fortify::username() => [trans('auth.failed')],
]);
}
/**
* Fire the failed authentication attempt event with the given arguments.
*
* #param \Illuminate\Http\Request $request
* #return void
*/
protected function fireFailedEvent($request)
{
event(new Failed(config('fortify.guard'), null, [
Fortify::username() => $request->{Fortify::username()},
'password' => $request->password,
]));
}
}
use Illuminate\Support\Facades\Auth;
This line should be added at the top of the AttemptAuthenticate.php file.
I had the same error and i solved by adding these lines. use Illuminate\Support\Facades\Auth; use App\Providers\RouteServiceProvider;
Add this to your AttemptToAuthenticate.php file.
use Illuminate\Support\Facades\Auth;
This works fine for me.

Laravel middleware auth throwing 500 in production but not in local

(laravel 5.4.36)
When I add 'middleware' => 'auth' to any of my routes, I get the three errors below. This only happens on my Production server. On my local it redirects to my login page as it should.
Example route:
Route::get('/credits', [
'middleware' => 'auth',
'as' => 'pages.credits',
'uses' => 'PagesController#credits'
]);
Symfony\Component\Debug\Exception\FatalThrowableError Call to a member
function setCookie() on null
Symfony\Component\Debug\Exception\FatalThrowableError Type error:
Argument 1 passed to
Illuminate\Session\Middleware\StartSession::addCookieToResponse() must
be an instance of Symfony\Component\HttpFoundation\Response, instance
of Illuminate\View\View given, called in
../vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php
on line 72
Symfony\Component\Debug\Exception\FatalThrowableError Type error:
Argument 1 passed to
Illuminate\Cookie\Middleware\EncryptCookies::encrypt() must be an
instance of Symfony\Component\HttpFoundation\Response, instance of
Illuminate\View\View given, called in
../vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php
on line 59
/vendor/laravel/framework/illuminate/Auth/Middleware/Authenticate.php:
<?php
namespace Illuminate\Auth\Middleware;
use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Factory as Auth;
class Authenticate
{
/**
* The authentication factory instance.
*
* #var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* Create a new middleware instance.
*
* #param \Illuminate\Contracts\Auth\Factory $auth
* #return void
*/
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string[] ...$guards
* #return mixed
*
* #throws \Illuminate\Auth\AuthenticationException
*/
public function handle($request, Closure $next, ...$guards)
{
$this->authenticate($guards);
return $next($request);
}
/**
* Determine if the user is logged in to any of the given guards.
*
* #param array $guards
* #return void
*
* #throws \Illuminate\Auth\AuthenticationException
*/
protected function authenticate(array $guards)
{
if (empty($guards)) {
return $this->auth->authenticate();
}
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
return $this->auth->shouldUse($guard);
}
}
throw new AuthenticationException('Unauthenticated.', $guards);
}
}

How do I extend Laravel Sanctum's functionality?

I am specifically trying to get Sanctum's Guard class to look for the API token in a JSON request body if it can't find it in the Authorization header. I simply need to add an elseif after it checks for the bearer token.
So question is: What is the best way to override this method (or class) with my own, without touching the original Sanctum files?
<?php
namespace Laravel\Sanctum;
use Illuminate\Contracts\Auth\Factory as AuthFactory;
use Illuminate\Http\Request;
class Guard
{
/**
* The authentication factory implementation.
*
* #var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* The number of minutes tokens should be allowed to remain valid.
*
* #var int
*/
protected $expiration;
/**
* Create a new guard instance.
*
* #param \Illuminate\Contracts\Auth\Factory $auth
* #param int $expiration
* #return void
*/
public function __construct(AuthFactory $auth, $expiration = null)
{
$this->auth = $auth;
$this->expiration = $expiration;
}
/**
* Retrieve the authenticated user for the incoming request.
*
* #param \Illuminate\Http\Request $request
* #return mixed
*/
public function __invoke(Request $request)
{
if ($user = $this->auth->guard('web')->user()) {
return $this->supportsTokens($user)
? $user->withAccessToken(new TransientToken)
: $user;
}
if ($token = $request->bearerToken()) {
$model = Sanctum::$personalAccessTokenModel;
$accessToken = $model::where('token', hash('sha256', $token))->first();
if (! $accessToken ||
($this->expiration &&
$accessToken->created_at->lte(now()->subMinutes($this->expiration)))) {
return;
}
return $this->supportsTokens($accessToken->tokenable) ? $accessToken->tokenable->withAccessToken(
tap($accessToken->forceFill(['last_used_at' => now()]))->save()
) : null;
}
}
/**
* Determine if the tokenable model supports API tokens.
*
* #param mixed $tokenable
* #return bool
*/
protected function supportsTokens($tokenable = null)
{
return in_array(HasApiTokens::class, class_uses_recursive(
$tokenable ? get_class($tokenable) : null
));
}
}
I don't know if you've already figured out but I think you need to add an entry in your AppServiceProvider boot method and override configureGuard functionality placed in SanctumServiceProvider at line 94.
app/Providers/AppServiceProvider.php
Auth::resolved(function ($auth) {
$auth->extend('sanctum', function ($app, $name, array $config) use ($auth) {
return tap($this->createGuard($auth, $config), function ($guard) {
$this->app->refresh('request', $guard, 'setRequest');
});
});
});
You will also need to override createGuard function to specify your custom Guard class with the functionality you require.

Laravel rate limit to return a JSON payload

I'm building an API on top of Laravel. I'd like to use the built in rate limiting capabilities by using the Throttle middleware.
Problem is, when the throttle middleware triggers the response is:
// Response headers
Too Many Attempts.
My API uses an error payload in JSON that looks like the following:
// Response headers
{
"status": "error",
"error": {
"code": 404,
"message": "Resource not found."
}
}
What is the best way to get the Throttle middleware to return an output in the manner I require?
Make your own shiny middleware, extend it by original, and override methods you like to be overriden.
$ php artisan make:middleware ThrottleRequests
Open up kernel.php and remove (comment out) original middleware and add yours.
ThrottleRequests.php
<?php
namespace App\Http\Middleware;
use Closure;
class ThrottleRequests extends \Illuminate\Routing\Middleware\ThrottleRequests
{
protected function buildResponse($key, $maxAttempts)
{
return parent::buildResponse($key, $maxAttempts); // TODO: Change the autogenerated stub
}
}
kernel.php
.
.
.
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
//'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
//'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'throttle' => \App\Http\Middleware\ThrottleRequests::class
];
I know it is a very old question but I have a very short solution for this problem.
Instead of creating new middleware, we can catch and handle the ThrottleRequestsException inside the Handler.php and return the JSON response accordingly.
app\Exceptions\Hanlder.php
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
use Request;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Exceptions\ThrottleRequestsException;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* #var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* #var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Report or log an exception.
*
* #param \Throwable $exception
* #return void
*
* #throws \Throwable
*/
public function report(Throwable $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Throwable $exception
* #return \Symfony\Component\HttpFoundation\Response
*
* #throws \Throwable
*/
public function render($request, Throwable $exception)
{
if ($exception instanceof ThrottleRequestsException && $request->wantsJson()) {
return json_encode([
'message' => 'Too many attempts, please slow down the request.',
'status' => false
]);
}
return parent::render($request, $exception);
}
}
Create a new file ApiThrottleRequests.php in app/Http/Middleware/ and paste the code below:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Cache\RateLimiter;
use Symfony\Component\HttpFoundation\Response;
class ApiThrottleRequests
{
/**
* The rate limiter instance.
*
* #var \Illuminate\Cache\RateLimiter
*/
protected $limiter;
/**
* Create a new request throttler.
*
* #param \Illuminate\Cache\RateLimiter $limiter
*/
public function __construct(RateLimiter $limiter)
{
$this->limiter = $limiter;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param int $maxAttempts
* #param int $decayMinutes
* #return mixed
*/
public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1)
{
$key = $this->resolveRequestSignature($request);
if ($this->limiter->tooManyAttempts($key, $maxAttempts, $decayMinutes)) {
return $this->buildResponse($key, $maxAttempts);
}
$this->limiter->hit($key, $decayMinutes);
$response = $next($request);
return $this->addHeaders(
$response, $maxAttempts,
$this->calculateRemainingAttempts($key, $maxAttempts)
);
}
/**
* Resolve request signature.
*
* #param \Illuminate\Http\Request $request
* #return string
*/
protected function resolveRequestSignature($request)
{
return $request->fingerprint();
}
/**
* Create a 'too many attempts' response.
*
* #param string $key
* #param int $maxAttempts
* #return \Illuminate\Http\Response
*/
protected function buildResponse($key, $maxAttempts)
{
$message = json_encode([
'error' => [
'message' => 'Too many attempts, please slow down the request.' //may comes from lang file
],
'status' => 4029 //your custom code
]);
$response = new Response($message, 429);
$retryAfter = $this->limiter->availableIn($key);
return $this->addHeaders(
$response, $maxAttempts,
$this->calculateRemainingAttempts($key, $maxAttempts, $retryAfter),
$retryAfter
);
}
/**
* Add the limit header information to the given response.
*
* #param \Symfony\Component\HttpFoundation\Response $response
* #param int $maxAttempts
* #param int $remainingAttempts
* #param int|null $retryAfter
* #return \Illuminate\Http\Response
*/
protected function addHeaders(Response $response, $maxAttempts, $remainingAttempts, $retryAfter = null)
{
$headers = [
'X-RateLimit-Limit' => $maxAttempts,
'X-RateLimit-Remaining' => $remainingAttempts,
];
if (!is_null($retryAfter)) {
$headers['Retry-After'] = $retryAfter;
$headers['Content-Type'] = 'application/json';
}
$response->headers->add($headers);
return $response;
}
/**
* Calculate the number of remaining attempts.
*
* #param string $key
* #param int $maxAttempts
* #param int|null $retryAfter
* #return int
*/
protected function calculateRemainingAttempts($key, $maxAttempts, $retryAfter = null)
{
if (!is_null($retryAfter)) {
return 0;
}
return $this->limiter->retriesLeft($key, $maxAttempts);
}
}
Then go to your kernel.php file in app/Http/ directory and replace
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
with
'throttle' => \App\Middleware\ApiThrottleRequests::class,
and use it
middleware('throttle:60,1')
or add
'apiThrottle' => \App\Http\Middleware\ApiThrottleRequests::class,
and you use this way
middleware('apiThrottle:60,1')
and help link
https://thedevsaddam.gitbooks.io/off-time-story/how_to_customize_laravel_throttle_message_response.html
So what I did was create a custom middleware that extends the ThrottleRequest middleware. You can override the handle function to check the request and see if it expects JSON as a response. If so, call the buildJsonResponse function which will format a JSON 429 response. You can tailor the JsonResponse in buildJsonResponse to suit your API needs.
This allows your throttle middleware to handle both JSON and other responses. If the request is expecting JSON, it will return a JSON response, but otherwise, it will return the standard "Too Many Attempts" plaintext response.
Please note this came from the Laravel 5.6.x ThrottleRequests middleware, and it is possible that the base class has changed since then.
EDIT:
Fixed call to the wrong method in the case where the request isn't expecting JSON response, but the request is throttled. Should be $this->buildException($key, $maxAttempts); not $this->buildResponse($key, $maxAttempts);
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Routing\Middleware\ThrottleRequests;
class ThrottlesRequest extends ThrottleRequests
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param int $maxAttempts
* #param float|int $decayMinutes
* #return mixed
*/
public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1)
{
$key = $this->resolveRequestSignature($request);
if ($this->limiter->tooManyAttempts($key, $maxAttempts, $decayMinutes)) {
// If the request expects JSON, build a JSON response, otherwise build standard response
if ($request->expectsJson()) {
return $this->buildJsonResponse($key, $maxAttempts);
} else {
return $this->buildException($key, $maxAttempts);
}
}
$this->limiter->hit($key, $decayMinutes);
$response = $next($request);
return $this->addHeaders(
$response, $maxAttempts,
$this->calculateRemainingAttempts($key, $maxAttempts)
);
}
/**
* Create a 'too many attempts' JSON response.
*
* #param string $key
* #param int $maxAttempts
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function buildJsonResponse($key, $maxAttempts)
{
$response = new JsonResponse([
'error' => [
'code' => 429,
'message' => 'Too Many Attempts.',
],
], 429);
$retryAfter = $this->limiter->availableIn($key);
return $this->addHeaders(
$response, $maxAttempts,
$this->calculateRemainingAttempts($key, $maxAttempts, $retryAfter),
$retryAfter
);
}
}
For anyone on laravel 8:
RateLimiter::for("verify",function(Request $request){
return Limit::perMinute(2)->by($request->ip())->response(function(){
return response()->json(["state"=>false,"error"=>"Youve tried too many times"],200);
});
});
And then add the throttle middleware to your route

Categories