return a json error in middleware? - laravel-5

I'm building an app and I'm using laravel5 as webAPI.
When the webAPI is in Maintenance Mode, I want to return a json error to app and I will get the status code in app to show a suitable message.
I rewrite the laravel CheckForMaintenanceMode for somereason and registed it in Kernel.
I write
if ($this->app->isDownForMaintenance()) {
$ip = $request->getClientIp();
$allowIp = "111.222.333.444";
if ($allowIp != $ip) {
return response()->json(['error' => "Maintenance!!"], 503);
}
}
return $next($request);
But I can get NOTHING in app side.I cannot get the message, the satus....
I writh the same code like return response()->json(['error' => "errormessage"], 422); in controller and I can get the message.status.. in app but I cannot do the same thing in a middleware.
why? how to do it?

This worked:
if ($this->app->isDownForMaintenance()) {
$ip = $request->getClientIp();
$allowIp = "111.222.333.444";
if ($allowIp != $ip) {
return response(['Maintenance'], 503);
}
}
return $next($request);
And not register the middleware in Kernel global HTTP middleware but put it in the route(api.php),like:
Route::group(['middleware' => 'maintenance'], function(){******}
I really donot know why but this worked for me.

Full example
public function handle($request, Closure $next)
{
if($request->token == "mytoken")
return $next($request);
else return response(['Token mismatch'],403);
}
Explanation
The response of a middleware
must be an instance of Symfony\Component\HttpFoundation\Response
so, for return a json, you have to do this
return response(['Token mismatch'],403);
The middleware must be registered in Kernel.php

The cleaner way to do it is to extend the
Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode class
and change it as per our needs and update the App\Http\Kernel.php like so..
App\Http\CustomMaintanceMiddleware.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode;
class CustomMaintanceMiddleware extends CheckForMaintenanceMode
{
public function handle($request, Closure $next)
{
if ($this->app->isDownForMaintenance()) {
return response(['Maintenance'], 503);
}
return $next($request);
}
}
Kernel.php
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class
];
TO
protected $middleware = [
\App\Http\CustomMaintanceMiddleware::class
];

Related

Authorize function is not working with middleware | Laravel

I have an authorization using middleware where Function could only run when authorized
this is my middleware:
class IsAdmin
{
public function handle($request, Closure $next)
{
if (auth()->check() && auth()->user()->is_admin == 1) {
return $next($request);
}
return abort(403, 'Forbidden');
}
}
my Controller:
public function destroy(int $bookId, int $reviewId, Request $request)
{
// #TODO implement
$check_bookReview = BookReview::firstWhere('id', $reviewId)->where('book_id', $bookId);
if ($check_bookReview && isAdmin()) {
BookReview::destroy($reviewId);
return response()->noContent();
} else {
abort(404);
}
}
and my api.php as well my Kernel:
'auth.admin' => \App\Http\Middleware\IsAdmin::class
Route::group(['middleware' => ['auth.admin']], function (){
Route::post('/books', 'BooksController#store');
Route::post('/books/{id}/reviews', 'BooksReviewController#store');
Route::delete('/books/{bookId}/reviews/{reviewId}', 'BooksReviewController#destroy');
});
and i have a User db field where it contains api_token and is_admin like below:
and my Postman still return 403 forbidden while i already gave an authorization by headers:
what should i do here, to fulfill my function?
Looks like your Authenticate middleware is not working, so it likely fails on auth()->check().
Make sure to use the auth middleware from Laravel, you can also use a guard as described here:
https://laravel.com/docs/9.x/authentication#protecting-routes

Login and verify user - Call to a member function getKey() on null

I am trying to create a custom verification flow, where as soon as a user clicks the verification link, it logs him in and also verifies him, instead of first making him log in and only then the verification link works.
I built a custom notification URL in my CustomVerificationNotification, including the registered user_id, to login him later:
protected function verificationUrl($notifiable)
{
if (static::$createUrlCallback) {
return call_user_func(static::$createUrlCallback, $notifiable);
}
return URL::temporarySignedRoute(
'verification.custom-verify',
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
[
'id' => $notifiable->getKey(),
'hash' => sha1($notifiable->getEmailForVerification()),
'user_id' => $this->user->id
]
);
}
Then in my web.php I added this route:
Route::get('/email/verify/{id}/{hash}/{user_id}','Auth\CustomVerifyController#login_and_verify')->name('verification.custom-verify');
Then in my CustomVerifyController:
public function login_and_verify(EmailVerificationRequest $request)
{
//..
}
But I get Call to a member function getKey() on null. And I can't edit EmailVerificationRequest, so what can I do? Is it possible to somehow call Auth::login($user); before calling the EmailVerificationRequest? (Because I have the user_id from the route)
I tried to follow the best answer from this post as well: How to Verify Email Without Asking the User to Login to Laravel
But I'm not sure then how to trigger the verify() method from the web.php and send the $request when I'm first calling the verify_and_login method
First you need verify that the URL is signed by adding the middleware signed
You don't want that anoyone having the url /email/verify/{id}/{hash}/{user_id} able to access this ressource without the signature.
web.php
Route::get('/email/verify/{id}/{hash}/{user_id}','Auth\CustomVerifyController#login_and_verify')
->middleware('signed')
->name('verification.custom-verify');
Then you need to verify that the hash correspond the user_id and for that you can use a Request or a Middleware. I think the Request fits better since Laravel already uses a Request for this.
CustomEmailVerificationRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Auth\Events\Verified;
use Illuminate\Foundation\Http\FormRequest;
class EmailVerificationRequest extends FormRequest
{
public function authorize()
{
$user = User::findOrFail($this->route('id'));
if (! hash_equals((string) $this->route('hash'), sha1($user->getEmailForVerification()))) {
return false;
}
return true;
}
}
Finally you need to login with the user and set is email as verified
CustomVerifyController.php
public function login_and_verify(CustomEmailVerificationRequest $request)
{
$user = User::findOrFail($this->route('id'));
Auth::login($user);
$user->markEmailAsVerified();
event(new Verified($user));
...
}
[Edit to add addition feature from comments]
In order to have a middleware that verify the signed URL and resend automatically the verification email, you need to build a custom middleware.
ValidateSignatureAndResendEmailVerification.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Routing\Exceptions\InvalidSignatureException;
use URL;
class ValidateSignatureAndResendEmailVerification
{
public function handle($request, Closure $next, $relative = null)
{
if(! URL::hasCorrectSignature($request, $relative !== 'relative')( {
throw new InvalidSignatureException;
}
if (URL::signatureHasNotExpired()) {
return $next($request);
}
return redirect()->route('resend-email-confirmation');
}
}
Then you need to add the middleware to Kernel.php
Kernel.php
protected $routeMiddleware = [
...
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'signed.email' => \App\Http\Middleware\ValidateSignatureAndResendEmailVerification::class,
...
];
Then, don't forget to update your route with the new middleware
web.php
Route::get('/email/verify/{id}/{hash}/{user_id}','Auth\CustomVerifyController#login_and_verify')
->middleware('signed.email')
->name('verification.custom-verify');

"Trying to get property 'headers' of non-object" Middleware\VerifyCsrfToken.php:180

I wrote my custom middleware, but when it is executed, the error appears.
Middleware:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
use App\Employee;
class CheckConfirm
{
public function handle($request, Closure $next)
{
if(Auth::check())
{
$id = Auth::id();
$empl = Employee::where('user_id','=', $id)->get();
if($empl->isEmpty())
{
return route('confirm');
}
else
{
dump($empl);
return $next($request);
}
}
else
{
return route('login');
}
}
}
When I try something like this:
if($empl===null)
{
return route('confirm');
}
сondition just doesn't work.
In this case, database queries are executed successfully.
Here is the error page with dump
Your middleware must return a Response object, or $next($request). As written, when not logged in or when $empl is empty, your middleware is just returning a string, not a redirect.
Update your returns to:
return redirect()->route('confirm');
and
return redirect()->route('login');
respectively.
It is very hard to fix, I tried to do it with App\Http\Middleware\VerifyCsrfToken::except, but not work.
My solution to this problem was creating a redirect to another route using App\Exception\Handler::render method.
if ($exception->getMessage() == "Trying to get property 'headers' of non-object") {
return redirect()->route('my.default.page');
}
Replace this line:
$empl = Employee::where('user_id','=', $id)->get();
if($empl->isEmpty()){ ... }
With this:
$empl = Employee::where('user_id', $id)->first();
if($empl){ ... }
Then dd() for each line, see where it fails. There may be an missing csrf token or the user is not logged..

laravel terminable middleware, pass parameters from controller

I want to use a terminable middleware for request logging:
<?php
namespace Illuminate\Session\Middleware;
use Closure;
use App\Helpers\Logger;
class LogRequest
{
public function handle($request, Closure $next)
{
return $next($request);
}
public function terminate($request, $response)
{
Logger::log($request, $response, $additionalInfo)
}
}
How can I pass the $additionalInfo from the controller to the middleware?
EDIT:
Unfortunately the additional info is generated in the controller. I therefore cannot hard code it in the route middleware function
Have you try to add to kernel.php:
protected $routeMiddleware = [
......
'LogRequest'=> \App\Http\Middleware\LogRequest::class
];
in the LogRequestMiddleware:
public function handle($request, Closure $next, $additionalInfo)
{
//here you have $additionalInfo
$request->attributes->add(["info" => $additionalInfo]);
return $next($request);
}
public function terminate($request, $response)
{
dd( $request->attributes);
}
And in controller:
public function __construct()
{
$additionalInfo = "test"
$this->middleware("LogRequest:$additionalInfo");
}
I think you can set some attribute to the request object in your controller while handling it, and the request object itself will be passed to terminate($request, $response) as the first parameter. Then you can extract whatever you set in your controller and use it.
Edited: You might be able to do this
Controller
$request->attributes->add(['additionalInfo' => 'additionalInfoValue']);
Middleware
public function terminate($request, $response)
{
$additionalInfo = $request->attributes('additionalInfo' => $additionalInfo);
Logger::log($request, $response, $additionalInfo)
}

Laravel route group middleware issue

I keep some laravel routes in the middleware auth group as:
Route::group(['middleware'=>'auth'],function(){
Route::controller('Activities', 'ActivitiesController');
Route::get('foo','FooController#getFoo');
.....
});
When I try to login to access these page, I am unable to login and url redirect to login page again and again. But If I use constructor as:
public function __construct()
{
$this->middleware('auth');
}
In those controllers It works perfectly. What is route group problem?
Route has a ::middleware class that you can use:
Routes > web.php
Route::middleware(['auth'])->group(function(){
Route::get('/activities', 'ActivitiesController#index');
});
You can also use Route::resource(); which I prefer. If you don't know what it does, here are the docs: https://laravel.com/docs/5.8/controllers#resource-controllers
This works for me , in route
Route::group(['middleware'=>'auth'],function(){
Route::controller('activities', 'ActivitiesController');
});
then controller
<?php namespace App\Http\Controllers;
class ActivitiesController extends Controller {
public function getIndex() {
return 'you are in;
}
}
on attempt to visit /activities I was redirected to login page , and on success back to \activities with 'you are in'.
In web.php:
$roleGeneral = role1.'~'.role2.'~'.role3.'~'.role4;
Route::group(['middleware' => ['permission.role:'.$roleGeneral]], function() {})
In Kernel.php:
protected $routeMiddleware = [...,
'permission.role' => \App\Http\Middleware\CheckPermission::class,
];
In CheckPermission.php:
public function handle($request, Closure $next, $role)
{
$roleArr = explode('~', $role);
$token = JWTAuth::getToken();
$user = JWTAuth::toUser($token);
$roleLogin = SysRoleModel::where('id', $user->role_id)->first();
if (in_array($roleLogin['name'], $roleArr)){
return $next($request);
}else{
return \Redirect::back()->withMessage('You are not authorized to access!');
}
}

Resources