laravel / framework

The Laravel Framework.
https://laravel.com
MIT License
32.15k stars 10.88k forks source link

[5.0] route model binding runing before middleware #6118

Closed aliuygur closed 8 years ago

aliuygur commented 9 years ago

Route model binding runing before middleware, this is bug.

GrahamCampbell commented 8 years ago

Doing your own route binding from a middleware isn't that bad tbh. Infact, I quite like that idea, and might even write one and publish it as a package. :)

lanort commented 8 years ago

Maybe this is not the smartest way but I just wrapped my bindings in the RouteServiceProvider.php in an if clause (App uses both: the check here and in the middleware)

if (Auth::check()) {
    // move the bindings here
}

EDIT: DOES NOT WORK. Oh gosh, does not work because Auth::check() will always return false at this stage.

soee commented 8 years ago

I think i have the same problem. Using model binding makes my global scope fail as i need to get in this scope authenticated user ID but it is always NULL in that case. So Laravel throws Trying to get property of non-object when calling Auth::user()->id in my global scope.

websanova commented 8 years ago

+1 for this fix. Auth should come before the route model binding. In my case I'm using JWT for auth, so definitely need the auth check first before route check...

In my case the api exposes certain parameters from a model based on privilege levels. So for instance a users profile only exposes an id and name to general public. But for the admins it exposes all fields.

amitailanciano commented 8 years ago

+1 since route binding comes first, the authenticated user doesn't exist in my user guard.

I feel like this is a pretty core use case -- @taylorotwell can you comment on why this issue was closed?

EDIT: Just read this: https://github.com/laravel/framework/issues/6118#issuecomment-149951364 however perhaps there ought to be some consideration into how this was implemented in the first place given that so many people encounter this use case.

It'd be great to see this functionality as part of the core route model binding feature, however it gets implemented.

Route::bind('event', function ($value, $route) {
    $event = EntityManager::getRepository(\App\Entities\Event::class)->findOneBy([
        'user' => auth('user')->user(),
        'id' => $value
    ]);

    if (!$event) {
        throw new NotFoundHttpException('Event entity not found');
    }

    return $event;
});
malhal commented 8 years ago

Another month gone by, anyone attempted to fix this show stopper?

aliuygur commented 8 years ago

Laravel is Taylor's personnel project not an open source protect. On Apr 30, 2016 12:39 AM, "Malcolm Hall" notifications@github.com wrote:

Another month gone by, anyone attempted to fix this show stopper?

— You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub https://github.com/laravel/framework/issues/6118#issuecomment-215888607

aliuygur commented 8 years ago

@rephole ben Go diline geçtim. Artık sikimde değil.

neyaoz commented 8 years ago

@alioygur Pire için yorgan yakılmaz, belki ölür bugün yarın.

simondubois commented 8 years ago

This blog post might help : https://murze.be/2016/05/using-current-user-route-model-binding/ Thanks @freekmurze

stevebauman commented 6 years ago

Just make sure the bindings or \Illuminate\Routing\Middleware\SubstituteBindings middleware is added after your authentication middleware.

poisa commented 6 years ago

I have another use case for this in multi-tenant sites. Let's say your model connects to your tenant db connection. This connection does not exist in the config file because it is dynamic; it depends on who is making the request. I need to authenticate the user and see which database tenant actually refers to. If model binding runs before my middleware, Laravel has no way to know which database to connect to.

@stevebauman's solution is quite right, alas it adds a bit of overhead in my particular design. But in my case, I removed the bindings middleware from all the groups, and added it manually to any route groups where I need model binding. It's more verbose that having this done by the framework, but still perfectly possible to achieve.

devinfd commented 6 years ago

This problem just bit me. I just learned that a user/attacker of my app was hitting an endpoint to see if records existed. Basically he was phishing the database. It wasn't really a DDOS attack because he was phishing each record manually so each request looked like normal. Long story short, once he learned which records did not exist he took that info to somebody else to complain about their work. I learned of this phishing from that third person that was concerned about how the attacker new which records were deleted. I'll spare you the details of why those records were deleted because the real issue seems to me that an UNAUTHENTICATED user was able to phish my database. THAT IS A SECURITY VULNERABILITY.

What is the actual reason that authentication does/can not happen before route model binding?

GuidoHendriks commented 6 years ago

@devinfd I think this issue has been resolved by now. Can't reproduce the behaviour you're talking about. You could try creating a new project and see if it still has this behaviour. If you find out it doesn't, it's probably in the configuration of your project.

If you believe you discovered a security vulnerability, you also shouldn't discuss it on Github, but email Taylor instead: https://laravel.com/docs/5.5/contributions#security-vulnerabilities

devinfd commented 6 years ago

@GuidoHendrik Thanks. My app is 5.5 and the bug does exists however I tried it on a fresh install of 5.5 and the bug did not exists. Long story short I discovered that the my app was using the old auth middleware 'auth' => \App\Http\Middleware\Authenticate::class. This was updated to 'auth' => \Illuminate\Auth\Middleware\Authenticate::class in 5.3 and I didn't catch that change. All is good now.

garygreen commented 6 years ago

@JosephSilber @taylorotwell I completely disagree that this use case should just be ignored - it's one of my real niggles with the framework at the moment; middlewares are fired after route model binding. This means that if you have a multi tenant application, or language-driven application, or global scopes, etc then your out of luck using the default built in route model binding:

$router->bind('account', function($id) {
   return Account::where('tenant_id', session('tenant_id'))->findOrFail();
});

Broke.

I can understand why the middlewares are fired after route model binding, but I don't believe it's outside the scope of the application to at least provide functionality to bind AFTER middlewares have fired. Something like:

Route::bindAfterMiddleware(function() {
   Route::bind('product', function($slug) {
      return Product::where('lang', session('lang_code', 'us'))->where('slug', $slug)->firstOrFail();
   });
});

Or for implicit route binding, hint which parameters should be bound after middlewares:

Route::bindAfterMiddleware('product');

Or for binding after session middleware only:

Route::bindAfterMiddleware('product', 'Illuminate\Session\Middleware\StartSession');

Something like that.

There really ought to be a way to cater for this without resorting to creating your own middleware to replace parameters in the url etc, otherwise it renders the whole route model binding feature quite useless in those cases. This would also help with any DDoS driven attacks based on urls as you could tell it to bind routes models AFTER throttler has run, etc.

Ref my suggestion: laravel/internals/issues/508

malhal commented 6 years ago

Loading in the models via binding before checking auth in middleware reminds me of Spectre!

garygreen commented 6 years ago

Loading in the models via binding before checking auth in middleware reminds me of Spectre!

Yah, it's definitely not ideal and I can totally understand the DDoS argument. Even if the throtter in Laravel would deny the request, your models have still already loaded and your more susceptible to a DDoS driven style attack on the database.

Like you said though, I wouldn't use that as an argument to fix this, though a fix would likely help with the DDoS issue too.

devinfd commented 6 years ago

@garygreen what Laravel version are you using? As I understand it, this was fixed in 5.3 however in order to receive that fix you must update the App\Http\Kernall $routeMiddleware auth key to use 'auth' => \Illuminate\Auth\Middleware\Authenticate::class

garygreen commented 6 years ago

@devinfd I think we're talking about different issues, this is nothing to do with auth. Route model binding is always fired before middlwares, even in Laravel 5.5.

garygreen commented 6 years ago

Nevermind, ignore me. I think since \Illuminate\Routing\Middleware\SubstituteBindings::class was added it just needs tweaking to be registered straight after session is initialised.

jpuck commented 6 years ago

Set Middleware Priority in App\Http\Kernel

For example, here I need my custom auth middleware to run first (before substitute bindings), so I unshift it onto the stack:

public function __construct(Application $app, Router $router)
{
    /**
     * Because we are using a custom authentication middleware,
     * we want to ensure it's executed early in the stack.
     */
    array_unshift($this->middlewarePriority, MyCustomApiAuthMiddleware::class);

    parent::__construct($app, $router);
}

Alternatively, you could override that entire priority structure if you needed explicit control (not recommended because you'll have to pay closer attention during upgrades to see if the framework changes). Specific to this issue is the SubstituteBindings class that handles route model binding, so just make sure your auth middleware comes sometime before that.

/**
 * The priority-sorted list of middleware.
 *
 * Forces the listed middleware to always be in the given order.
 *
 * @var array
 */
protected $middlewarePriority = [
    \App\Http\Middleware\MyCustomApiAuthMiddleware::class
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    \Illuminate\Auth\Middleware\Authenticate::class,
    \Illuminate\Session\Middleware\AuthenticateSession::class,
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
    \Illuminate\Auth\Middleware\Authorize::class,
];
sebastiaanluca commented 6 years ago

I rarely use(d) route binding, but for a new project I'm using https://github.com/Propaganistas/Laravel-FakeId which auto-resolves models from their fake ID and that kind of requires route binding. Of course, that's when I ran into this issue. Wanted to apply global scopes to those models before they were resolved, but Laravel does it the other way around. I can understand from a certain point of view, you want consistency. But sometimes that's a no-go security and usability-wise.

Here's my attempt at fixing the issue: https://gist.github.com/sebastiaanluca/d61e9e2bc874615f82cfd679dee8edce#gistcomment-2358542

Not a 100% covered and tested with this, but feedback welcome!

denifelixe commented 5 years ago

Good news come with laravel 5.3 above as https://laravel.com/docs/5.3/upgrade said in Binding Substitution Middleware section

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

So now, the Route Model Binding is handled by Illuminate\Routing\Middleware\SubstituteBindings in web middleware group.

Since the web middleware group always runs first, you can comment/remove this middleware in web middleware group and add it to routeMiddleware

protected $middlewareGroups = [
        'web' => [
                ...
                ...
                // \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
        ...
        ...
];

protected $routeMiddleware = [
        ...
        ...
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ...
];

after doing that, you can add the 'bindings' middleware in specific order in your route.

e.g

Route::middleware(['auth', 'bindings'])->group(function () {

    Route::get('/', 'Home\HomeController@index');

});

in that example, 'auth' middleware run first before 'bindings' middleware.

but, in below example, 'bindings' middleware run first before 'auth' middleware :

Route::middleware(['bindings', 'auth'])->group(function () {

    Route::get('/', 'Home\HomeController@index');

});

Hope it helps 👍

Stone624 commented 3 years ago

Adding a small tip to denifelixe's solution for this issue , I have a Route Group for Auth, Then Account Scopes (which sets scope for all models for the specific account type ie: only access owned resources, a custom middleware) , Then bindings : Route::middleware(['auth','account_type_scope','bindings'])->group(function(){ // Routes Here // });

This is nice because the vast majority of my routes are within this group, And you can just add another group with just binding middleware bindings surrounding all other public routes as well.

If you have an existing and complex routes structure (where this isn't necessarily easy to refactor or possible), Try adding the middleware Construct Method of every controller, which did solve this issue for me as well. :

ModelController Extends Controller {
    public function __construct(){ $this->middleware('bindings'); }
}