简体   繁体   中英

Laravel multi auth and authorize resource

I've got an issue with the following scenario an I hope to find advice here:

I've got Laravel 5.7 installed and ran make:auth for the authentication scaffolding. The native User model has been removed and instead I'm using two custom models "Customer" and "Admin" (migrations have been changed accordingly).

Admin model:

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Traits\FormatUserdata;

class Admin extends Authenticatable
{
    use Notifiable, FormatUserdata;

    protected $guard = 'admin';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'firstname', 'lastname', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];
}

Customer model:

namespace App;

use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Traits\FormatUserdata;

class Customer extends Authenticatable
{
    use HasApiTokens, Notifiable, FormatUserdata;

    protected $guard = 'customer';

    protected $fillable = [
        'firstname', 'lastname', 'email', 'company', 'address', 'zip', 'city', 'country', 'vatid', 'customer_no', 'password',
    ];

    protected $hidden = [
        'password', 'remember_token',
    ];

    public function licenses()
    {
        return $this->hasMany('App\License');
    }
}

So I have to authenticate users from two different models and tables. In order for this to work I changed my config/auth.php like so:

return [
    'defaults' => [
        'guard' => 'customer',
        'passwords' => 'customers',
    ],

    'guards' => [
        'customer' => [
            'driver' => 'session',
            'provider' => 'customers',
        ],

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

        'api' => [
            'driver' => 'passport',
            'provider' => 'admins',
        ],
    ],

    'providers' => [
        'customers' => [
            'driver' => 'eloquent',
            'model' => App\Customer::class,
        ],

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

    'passwords' => [
        'customers' => [
            'provider' => 'customers',
            'table' => 'password_resets',
            'expire' => 60,
        ],

        'admins' => [
            'provider' => 'admins',
            'table' => 'password_resets',
            'expire' => 60,
        ],
    ],

];

For my Customer model I set up a resource controller which is protected by a resource policy as well as the auth middleware for the index method:

namespace App\Http\Controllers;

use App\Customer;
use Illuminate\Http\Request;

class CustomerController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth:admin')->only('index');
        $this->authorizeResource(Customer::class);
    }

    public function show(Customer $customer)
    {}

    ...shortened

}

Here is the policy that I'm referencing in this controller:

namespace App\Policies;

use App\Customer;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Foundation\Auth\User;

class CustomerPolicy
{
    use HandlesAuthorization;

    public function view(User $user, Customer $customer)
    {
        return true;
    }

}

My routes do not have any additional middleware applied.

Here is the issue:

When I'm accessing my route /customers/1 with a user of the default guard "customer" everything works as expected (no error). However when I'm trying to access that route with a admin user I'll get a 403 unauthorized response. The strange thing is that when I'm trying to check the permissions in the view with

@can('view', App\Customer::findOrFail(3))

the policy works correctly. I've found that the view method of the policy isn't being called at all when I'm accessing the route as an admin. I digged a little bit through the laravel code and I'm pretty sure that the issue is related to the fact that the customer guard is default and the user resolver only the default.

Does anyone have any idea how to solve that problem?

Thank you! Andreas

edit:

Here is my AuthServiceProvicer.php:

namespace App\Providers;

use App\Policies\CustomerPolicy;
use App\Customer;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        'App\Model' => 'App\Policies\ModelPolicy',
        Customer::class => CustomerPolicy::class
    ];

    public function boot()
    {
        $this->registerPolicies();
    }
}

And here is my routes/web.php

Route::get('/', function () {
    return view('welcome');
});

Auth::routes(['register' => false]);

Route::prefix('admin')->name('admin.')->group(function () {
    Route::get('/', function () {
        return redirect( route('admin.dashboard') );
    });

    Route::get('dashboard', 'AdminController@dashboard')->name('dashboard');
    Route::get('login', 'Auth\AdminLoginController@showLoginForm')->name('login');
    Route::post('login', 'Auth\AdminLoginController@login')->name('login.post');
    Route::post('logout', 'Auth\AdminLoginController@logout')->name('logout');

    Route::post('password/email', 'Auth\AdminForgotPasswordController@sendResetLinkEmail')->name('password.email');
    Route::get('password/reset', 'Auth\AdminForgotPasswordController@showLinkRequestForm')->name('password.request');
    Route::post('password/reset', 'Auth\AdminResetPasswordController@reset')->name('password.update');
    Route::post('password/reset/{token}', 'Auth\AdminResetPasswordController@showResetForm')->name('password.reset');

    Route::resource('customers', 'CustomerController');
});

Route::prefix('backend')->name('backend.')->group(function () {
    Route::get('/', function () {
        return redirect( route('backend.dashboard') );
    });
    Route::get('dashboard', 'BackendController@dashboard')->name('dashboard');
    Route::resource('customers', 'CustomerController')->except([
        'index'
    ]);

});

So for customers I'm using the route /backend/customer/{customer} and for admins I'm using the route /admin/customer/{customer}

After a bit of further research I came across this awesome blog post

https://medium.com/@JinoAntony/multi-user-authentication-using-guards-in-laravel-5-6-f18b4e61bdde

The important step here is to create an additional middleware "AssignGuard" that sets the guard depending on the used route.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM