How to make middleware for all the routes - laravel

I have split my routes.php file into 5 different files (admin.php routes, client.php routes and so on). Now what I want is basically in each file, I got 100 routes for example). What I need is to use middleware and apply it to all the routes that exist in my app.
Solution 1) USE ROUTE GROUP and pass middleware there. If I do that, I would need to put all my routes in route::group and I have to write route:group in 5 different files.
Is there any way to write this middleware somewhere in one place and automatically globally apply it to all routes?

You can put it inside your Kernel (app/Http/Kernel.php).
/**
* The application's route middleware groups.
*
* #var array
*/
protected $middlewareGroups = [
'web' => [
...
\App\Http\Middleware\YourMiddleware::class,
],
];
Note that there is also another property named $middleware which is for every single route of your application.
For more information about middleware: https://laravel.com/docs/middleware#middleware-groups

Related

In Laravel, it seems the middleware is not applied immediately

I use Laravel 9.
I have this route, included in a group and middleware:
Route::middleware(['auth', 'ensureUserIsAdmin'])->group(function () {
.....
Route::resource('sports', SportController::class);
.....
});
The middleware is only to check if the user is or not an administrator.
When the connected user is not admin, and tries to go to this route for a known sport:
/sports/FOOT/edit
then he receives the response "FORBIDDEN". Perfect, the middleware made his job.
But when the same not admin user tries to go to a route for an unknown sport:
/sports/UNKNOWSPORT/edit
then he receives the response "NOT FOUND". Is it normal? It looks like the framework makes a database request and only after he applies the middleware.
What's wrong in my code?
Every route within web.php file is processed by web group middleware - you may find this in RouteServiceProvider class
Route::middleware('web')
->group(base_path('routes/web.php'));
This group defined within Kernel class and contains some app middleware. They're handled before any route specific middleware fires
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
So when you think your route has 'auth', 'ensureUserIsAdmin' as middleware only it is not quite true
In your case \Illuminate\Routing\Middleware\SubstituteBindings::class middleware is the one is failing and showing 404. And yes, it makes query to database if there are some route parameters in it
For simplicity, let say you have this route and only model with id=1 exists
Route::get('/sports/{sport}', function (Sport $sport) {
dump($sport);
});
/sports/1 is dumping model, sports/2 shows 404 as expected. Let's comment \Illuminate\Routing\Middleware\SubstituteBindings::class
Now both pages are actually dumping model, but sports/2 shows empty default model with no parameters
If you need to change this logic and show 403 for non-existing models, you may add middleware to a group BEFORE \Illuminate\Routing\Middleware\SubstituteBindings::class middleware
For example, lets' create simple middleware which always return 403 like
public function handle(Request $request, Closure $next)
{
abort(403);
}
And change web group to
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
AlwaysFalse::class, // HERE I AM
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
Now no matter what both pages will show 403
But this will be applied for EVERY route within routes/web.php file, so you may change the logic like
public function handle(Request $request, Closure $next)
{
// Applied only in any route named `sports.something` - resource routes including
// is_admin() function doesn't really exists, middleware logic is up to you
abort_if((Route::is('sports.*') && !is_admin()), 403);
return $next($request);
}
Now for admin user /sports/1 shows model, /sports/2 - 404 response.
For non-admin user both pages will return 403.
About is it normal or not - I think yes. Ask yourself - what can you do (what your access level) over a thing which is doesn't even exists? So it better define first does model really exists and after you're sure it is, make something with it. But it is just my opinion, someone may disagree
Hope it'll help

MethodNotAllowedHttpException path without CSRF

I pass a route in laravel which is called by a function in JS and I have no control of it because it is external, this route is a post method since I need it to be that way but it generates me a MethodNotAllowedHttpException as I do to exclude certain routes of this validation.
Note: I have already tried adding it in VerifyCsrfToken in its exception vector, modifying that class file VerifyCsrfToken extends Middleware to a new file called class VerifyCsrfToken extends BaseVerifier with all its dependencies and I have also disabled the validations in the Middleware but none of them works for me
From the docs:
You should place these kinds of routes outside of the web middleware group that the RouteServiceProvider applies to all routes in the routes/web.php file. However, you may also exclude the routes by adding their URIs to the $except property of the VerifyCsrfToken middleware:
protected $except = [
'your/route/*',
];
For this you have to add the URI of the route inside protected $except.
For example if you URL is www.example.com/full/route/
you have to add
protected $except = [
'/full/route/*',
];
Seems like you're adding route name but not the URI.

Laravel Route model binding - empty user

I have a route
http://192.168.10.15/user/33/edit
I am trying to return the user based on the url id.
public function edit($id, \App\User $user)
{
dd($user->id);
return view('user.update');
}
The id is returning null, how do I do this?
For route binding to work you should have type-hinted variable names match a route segment name, as the doc required :
Laravel automatically resolves Eloquent models defined in routes or
controller actions whose type-hinted variable names match a route
segment name. For example:
Route::get('api/users/{user}', function (App\User $user) {
return $user->email; });
Since the $user variable is type-hinted as the App\User Eloquent model
and the variable name matches the {user} URI segment, Laravel will
automatically inject the model instance that has an ID matching the
corresponding value from the request URI. If a matching model instance
is not found in the database, a 404 HTTP response will automatically be generated.
For your case :
Route::get('/users/{user}/edit', 'YourController#edit');
And in your controller :
public function edit(\App\User $user)
{
dd($user->id);
return view('user.update')->withUser($user);
}
If you recently upgraded to laravel 5.3 and above, you might want to check if you have updated laravel/app/Http/Kernel.php to register route middleware for binding substitution.
Route model binding is now accomplished using middleware. All
applications should add the
Illuminate\Routing\Middleware\SubstituteBindings to your web
middleware group in your app/Http/Kernel.php file:
\Illuminate\Routing\Middleware\SubstituteBindings::class,
You should also register a route middleware for binding substitution
in the $routeMiddleware property of your HTTP kernel:
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
Once this route middleware has been registered, you should add it to
the api middleware group:
'api' => [
'throttle:60,1',
'bindings',
],
I fixed it by adding named route. Model binding didn't work if there was no name specified for route.
Route::get('product/{product}', "ProductController#read")->name('product.read');

Laravel 5.3 and 5.4 Custom Route File

According to the Laravel Documentation, in routing section, the files located in the route directory are automatically loaded by the framework.
All Laravel routes are defined in your route files, which are located in the routes directory. These files are automatically loaded by the framework.
So, I tried to create another file in this directory called auth.php for handling my custom authentication routes. But, the routes defined in this file are not loaded.
It is possible to use this approach, or I need to register a service provider to load custom route files?
You need to map routes in your RouteServiceProvider.php, Check the example of web routes.
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* #return void
*/
protected function mapWebRoutes()
{
Route::group([
'middleware' => 'web',
'namespace' => $this->namespace,
], function ($router) {
require base_path('routes/web.php');
});
}

Laravel 5.1: using default Auth middleware globally

I'm trying to use the out-of-box Authenticate middleware globally except on auth/login and auth/logout, so that I don't need to add it in every single controller. I added it to the global middleware list in Kernel (as shown below); however, it gets stuck in an infinite auth/login redirect. For any guest, I want the page to be redirected to auth/login and stay there.
class Kernel extends HttpKernel
{
protected $middleware = [
...
\App\Http\Middleware\Authenticate::class,
];
}
It's happening because when it hits auth/login the first time, the global Authenticate kicks in and redirects to auth/login again over and over.
Is it possible to use the default Authenticate middleware globally as I've described? Do I need to create a new middleware for it?
Edit: I've concluded that Thomas' approach is good enough.
You can always use a Route Groups. In your routes.php file...
// Your login/logout routes
Route::get('login', 'Auth\AuthController#getLogin');
Route::post('login', 'Auth\AuthController#postLogin');
Route::get('logout', 'Auth\AuthController#getLogout');
Route::group(['middleware' => 'auth'], function() {
// Put all other routes here and the auth middleware will be applied on them all
});
Edit: Also, you do not need to add the Authenticate middleware to the global middleware stack. Just leave it in the default $routeMiddleware.
'auth' => \App\Http\Middleware\Authenticate::class,

Resources