laravel / framework

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

Authorization on new guard #12087

Closed minhchu closed 8 years ago

minhchu commented 8 years ago

I define new admin guard:

    'guards' => [
        ...
        'admin' => [
            'driver' => 'session',
            'provider' => 'admins',
        ],
    ],

    'providers' => [
        ...
        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Admin::class,
        ]
    ],

I also create new Booking policy and register in AuthServiceProvider:

    protected $policies = [
        'App\Booking' => 'App\Policies\Admin\BookingPolicy',
    ];

BookingPolicy has update method:

public function update($user, $booking)
{
     ...
}

When I call $this->authorize('update', $booking) in controller, $user is an instance of App\User. But I need to authorize the authenticated admin. I try to type-hint the parameter:

public function update(\App\Admin $user, $booking)
{
     ...
}

It throws an error:

Type error: Argument 1 passed to App\Policies\Admin\BookingPolicy::edit() must be an instance of App\Admin, instance of App\User given

How can we implement a function to change guard when working with a specific policy or ability.
Thanks

hiendv commented 8 years ago

From the documentation: https://laravel.com/docs/5.2/authorization#controller-authorization

The AuthorizesRequests trait also provides the authorizeForUser method to authorize an action on a user that is not the currently authenticated user:

$this->authorizeForUser($user, 'update', $post);

Gate::forUser() should get the guard instance for the given user.

donnysim commented 8 years ago

Gate::forUser() should get the guard instance for the given user.

But you can't specify that for blade making blade directives useless when using multi-auth.

minhchu commented 8 years ago

Thanks all.

pdbreen commented 8 years ago

Just had a very similar issue trying to handle fully independent auth for admins and customers (different DB models and drivers). What got the @can blade helpers working for me was to update the userResolver closure when my custom auth provider got instantiated. This is in the boot() method of my AuthServiceProvider class. Basically, I supply a default guard that's consistent with the guard being used for that type of auth. In my case, each request is fully served by one or the other provider - never both. So, this works nicely.

        \Auth::provider('customer_auth', function ($app, array $config) {
            $app['auth']->resolveUsersUsing(function ($guard = 'customer') use ($app) {
                return $app['auth']->guard($guard)->user();
            });
            return new CustomerUserProvider($app['hash'], $config['model']);
        });

        \Auth::provider('admin_auth', function ($app, array $config) {
            $app['auth']->resolveUsersUsing(function ($guard = 'admin') use ($app) {
                return $app['auth']->guard($guard)->user();
            });
            return new AdminUserProvider($app['hash'], $config['model']);
        });
zek commented 8 years ago

I have same problem. Maybe we can add a guard attribute to the controller.

My solution is adding that line to middleware

    config()->set('auth.defaults.guard','panel');
reefki commented 8 years ago

@drtzack Thanks for the solution. I place the code on Authenticate middleware:

config()->set('auth.defaults.guard', $guard);

With this I can simply use Auth::user() instead of Auth::guard('custom_guard')->user() to access logged in user on different guard.

Hope that we can set guard attribute on a controller as you suggested.

matt-allan commented 8 years ago

If anyone else is trying to do this, I wrote a solution to allow setting the guard per route group.

Add this to your AppServiceProvider's boot method:

$this->app['router']->matched(function (\Illuminate\Routing\Events\RouteMatched $e) {
    $route = $e->route;
    if (!array_has($route->getAction(), 'guard')) {
        return;
    }
    $routeGuard = array_get($route->getAction(), 'guard');
    $this->app['auth']->resolveUsersUsing(function ($guard = null) use ($routeGuard) {
        return $this->app['auth']->guard($routeGuard)->user();
    });
    $this->app['auth']->setDefaultDriver($routeGuard);
});

Now you can specify the guard like this:

Route::group(['guard' => 'my-guard'], function () {
    // ...
});
nwrman commented 8 years ago

@yuloh This is exactly what I was looking for!, took me forever to figure this one out!

matt-allan commented 8 years ago

In 5.3 the auth middleware does this automatically. So if you do auth:api the api guard will be used as the default. 😀

malhal commented 7 years ago

@yuloh there is still an issue if you don't want to use a guard for the api, so for example you want to allow users and guests. You still need a way to change the default Auth Guard to api so that Auth:user() works everywhere as expected, e.g. in Policies.

cweiske commented 7 years ago

We experienced this bug, too.

Our configuration has multiple auth guards, and inside a route that uses the non-default auth guard, Auth::user() did never work because it tried to fetch the user with the default guard.

yuloh's hack works very fine. Thanks!

jamesgraham commented 7 years ago

@malhal do you have an example of how this works with routes that can accept multiple guards?

lucassena commented 7 years ago

@yuloh, Is there a way I can specify which login page will be redirected if the user is not authenticated with laravel 5.3?

I can't use auth: admin because if it is not logged in, it will be redirected to "/login" and not "/admin/ login".

malhal commented 7 years ago

@jamesgraham apologies for the delay I'm not sure about multiple guards.

sebastiansulinski commented 7 years ago

@lucassena - not sure if that's what you're after, but you can specify which page user is redirected to (if not authenticated) based on the guard from within the App\Exceptions\Handler::unauthenticated - here's what I've got:

protected function unauthenticated($request, AuthenticationException $exception)
{
    if ($request->expectsJson()) {
        return response()->json(['error' => 'Unauthenticated.'], 401);
    }

    $route = in_array('admin', $exception->guards())
            ? route('admin.login')
            : route('front.login');

    return redirect()->guest($route);
}

Also, for the App\Http\Middleware\RedirectIfAuthenticated I've used $guard arguments to dictate which route should be used, but this obviously depends on how you name your routes:

public function handle($request, Closure $next, $guard = null)
{
    $path = $guard ?: 'front';

    if (Auth::guard($guard)->check()) {
        return redirect(route($path.'.dashboard'));
    }

    return $next($request);
}

Alternatively you could use some config file, which stores these and again - use guard name to select individual path by index.

lucassena commented 7 years ago

Thankyou, @sebastiansulinski! That's exactly what I did. Anyway, thanks for your response, I'm glad that I had followed the right path!

sebastiansulinski commented 7 years ago

No problem at all @lucassena - I'm glad you managed to resolve it.

pgyf commented 3 years ago
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Foundation\Application;

class AuthGuard
{
    /**
     * The application implementation.
     *
     * @var \Illuminate\Contracts\Foundation\Application
     */
    protected $app;

    /**
     * Create a new middleware instance.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public function __construct(Application $app)
    {
        $this->app = $app;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next, $guard = 'web')
    {
        $this->app['auth']->setDefaultDriver($guard);
        return $next($request);
    }
}
Route::group([
            'prefix'     => '',
            'middleware' => ['web', 'auth.guard:admin'],
], function ($router) {
});