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
Related
I'm trying to prevent some users from accessing admin panel.
I have created a middleware with this code called RequireAdminRole
public function handle($request, Closure $next, $guard = null)
{
$user = Auth::user();
dd($user);
if (!$user) {
return $next($request);
}
if (!$user->hasRole('Admin')) {
// if your sessions are decoupled from the frontend
// you can even logout the user like so:
// auth()->logout();
abort(403, 'Access denied');
}
return $next($request);
}
I have added it to kernel.php
'web' => [
\App\Http\Middleware\RequireAdminRole::class,
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
My problem id $user has null value, so I can't get user permissions.
Can anybody help me?
Best regards.
The order of your middleware is important, the middleware handlers are executed in the order that they are defined. This means that you are trying to retrieve the authenticated user from the session before the StartSession middleware has been executed.
So to solve your issue, you should move your RequireAdminRole middleware to the end of the array, or at least after the definition of StartSession.
'web' => [
// Other middleware definitions...
\App\Http\Middleware\RequireAdminRole::class
],
Is it possible to protect post method with CSRF token with laravel header() method ?
just like
$obj->header('Content-Type', 'text/xml');
Every routes register within your app in the web.php file by default is already protect by the VerifyCsrfToken. In the App\Providers\RouteServiceProvider the method mapWebRoutes attach on every routes in the web.php file the web middleware which is a wrapper of many middleware like you can see in the App\Http\Kernel class
protected $middlewareGroups = [
'web' => [
// others middlewares goes here
\App\Http\Middleware\VerifyCsrfToken::class,
// others middlewares goes here
],
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');
When i want to access to a page in my application i get an exception when i'm not logged in
ErrorException in ec75fc198343b0a46e649467428bd2d5f829caf1.php line 49:
Trying to get property of non-object
Should i make a route middleware group ?
I would like when i want to access to the pages when i'm not logging that the app redirect me directly to the login form without display me this error message !
Someone know how to do that ?
Enable the auth middleware for the action that is responsible for rendering the page. There are multiple ways how you can accomplish this in Laravel. Here are a few of them:
In the routes file for a particular action
Route::get('your/page', ['middleware' => 'auth', 'uses' => 'YourPageController#index']);
or the same thing fluently
Route::get('your/page', 'YourPageController#index')->middleware('auth');
In the routes file for a group of actions/pages
Route::group(['middleware' => ['auth'], function () {
Route::get('your/page', 'YourPageController#index');
});
In the controller
class YourPageController extends Controller
{
public function __construct()
{
$this->middleware('auth'); // you can pass an array of actions/methods that should not be covered by the auth middleware in a second parameter ['except' => 'someaction']
}
}
Edit your Kernel.php and add this to the protected $routeMiddleware part at the end:
'auth' => \App\Http\Middleware\Authenticate::class,
Then in your routes you can use this 'auth' to check if the user is logged in.
For example:
Route::get('/example', 'YourController#getExample')->middleware('auth');
if you don't have a middleware or you have any trouble follow this https://laravel.com/docs/5.4/authentication
Include the Middleware in routes.
Route::get('/Demo','DemoLine#index')->middleware('auth');
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,