_Marco_'s avatar

Spatie Permissions and multiple guards

Hi guys, I am having some problems with Spatie permissions library.

I have a users table and 2 models which are User and Hairdresser using the same db table. Both have the same signup and login logic the only difference is that they will have different roles(one will have User Role and other Hairdresser Role). The problem is that when I signup the User everything works fine and the role User is assigned but with the exact same code when I signup hairdresser the role is assigned and in response I get it's role but when I try to access hairdresser in a get petition roles come empty. I believe this haves something to do with guards in auth.php but I tried everything and nothing works.

auth.php

​```

return [

'defaults' => [ 'guard' => 'web', 'passwords' => 'users', ],

'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users',

],
'web2' => [
  'driver'   => 'session',
  'provider' => 'hairdressers',
],

'api'  => [
  'driver'   => 'passport',
  'provider' => 'users',
  'hash'     => false,
],

],

'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\Models\User\User::class, ],

'hairdressers' => [
  'driver' => 'eloquent',
  'model'  => App\Models\Hairdressers\Hairdresser::class,
],

],

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

'password_timeout' => 10800,

];

​```

1 like
18 replies
MichalOravec's avatar

Your models are like this?

App\Models\Hairdressers\Hairdresser::class

App\Models\User\User::class

Maybe it should be like this

App\Models\Hairdresser::class

App\Models\User::class
_Marco_'s avatar

Hi, yes thats the right path and they both work because if I swap them around then users doesn't work and Hairdressers does

MichalOravec's avatar

You have to change guard when you login your users or hairdressers.

/**
 * Get the guard to be used during authentication.
 *
 * @return \Illuminate\Contracts\Auth\StatefulGuard
 */
protected function guard()
{
    return Auth::guard('web'); // for users 
    
    return Auth::guard('web2'); // for hairdressers
}

This code you have to put to you LoginController, but you use same LoginController for both.

_Marco_'s avatar

I just have a folder after Models because some folders have multiple models inside but the path is correct.

_Marco_'s avatar

I have the guard_name defined in my models like so:

Users Model:

protected $guard_name = 'web';

or Hairdresser Model:

protected $guard_name = 'web2';

MichalOravec's avatar

It works only for one of your guards because in auth.php is

'defaults' => [
    'guard' => 'web',
    'passwords' => 'users',
],

So you have to set your guard in your LoginController, but propably you need to have two different login forms and also two different LoginControllers.

1 like
_Marco_'s avatar

I have 2 different loginControllers since I am using HairdresserController for register and login and UserController for the same purpose but no form since I am building an API Rest and testing stuff on Postman but I have different endpoints

MichalOravec's avatar

So in LoginController for hairdressers add

/**
 * Get the guard to be used during authentication.
 *
 * @return \Illuminate\Contracts\Auth\StatefulGuard
 */
protected function guard()
{    
    return Auth::guard('web2');
}

Of course it will be work if your LoginControllers use this trait AuthenticatesUsers

_Marco_'s avatar

My HairdresserController,UserController looks pretty much the same but in store I assign the role User


namespace App\Http\Controllers;

use App\Http\Requests\Users\HairdresserRequest;
use App\Http\Resources\Users\HairdresserResource;
use App\Models\Hairdressers\Hairdresser;
use App\Repositories\Users\HairdresserRepository;
use Carbon\Carbon;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;

class HairdresserController extends Controller
{

  protected $hairdresser_repo;

  public function __construct(HairdresserRepository $hairdresser_repository)
  {
    $this->hairdresser_repo = $hairdresser_repository;
    
  }
  public function signup(HairdresserRequest $request)
  {
    try {
      DB::beginTransaction();

      /*  ***Store image in disk*** */

      $image           = $request->file('image');
      $image_path_name = time() . $image->getClientOriginalName();
      Storage::disk('hairdressers')->put($image_path_name, File::get($image));

      $data = [
        'name'     => $request->name,
        'surname'  => $request->surname,
        'email'    => $request->email,
        'mobile'   => $request->mobile,
        'password' => bcrypt($request->password),
        'image'    => time() . $request->file('image')->getClientOriginalName(),
      ];

      $query = $this->hairdresser_repo->create($data)
        ->assignRole('Hairdresser');

      $hairdresser = new HairdresserResource($query);
      $body        = compact('hairdresser');

      DB::commit();

      return response()->json(compact('body'), 201);

    } catch (Exception $e) {
      DB::rollback();
      dd($e);
      $message = 'Something went wrong';
      throw new Exception($message, 500);
    }
  }

  /**
   * Update hairdresser
   *
   */

  public function update(HairdresserRequest $request, $id)
  {
    try {
      DB::beginTransaction();
      $user_id = Auth::id();
      if ($user_id == $id) {
        /*  ***Store image in disk*** */
        /* $image = $request->file('image');
        $image_path_name = time() . $image->getClientOriginalName();
        Storage::disk('users')->put($image_path_name, File::get($image));*/

        $data = [
          'name'    => $request->name,
          'surname' => $request->surname,
          'mobile'  => $request->mobile,
          //'password' => bcrypt($request->password),
          //'image' => time() .  $request->file('image')->getClientOriginalName()
        ];

        $query       = $this->hairdresser_repo->model()::find($id)->update($data);
        $hairdresser = $this->hairdresser_repo->model()::find($id);
        $hairdresser = new HairdresserResource($hairdresser);
        $body        = compact('hairdresser');

        DB::commit();

        return response()->json(compact('body'), 202);
      } else {
        return response()->json([
          'message' => 'Tienes que estar identificado',
        ]);
      }

    } catch (Exception $e) {
      DB::rollback();
      dd($e);
      $message  = 'Something went wrong';
      $errors[] = $e->getMessage();
      throw new Exception($message, 500);
    }
  }

  /**
   * Login user and create token
   *
   */
  public function login(Request $request)
  {
    $request->validate([
      'email'       => 'required|string|email',
      'password'    => 'required|string',
      'remember_me' => 'boolean',
    ]);

    $credentials = request(['email', 'password']);

    if (!Auth::attempt($credentials)) {
      return response()->json([
        'message' => 'Unauthorized',
      ], 401);
    }

    $user = $request->user();

    $tokenResult = $user->createToken('Personal Access Token');
    $token       = $tokenResult->token;

    if ($request->remember_me) {
      $token->expires_at = Carbon::now()->addWeeks(1);
    }

    $token->save();

    return response()->json([
      'access_token' => $tokenResult->accessToken,
      'token_type'   => 'Bearer',
      'expires_at'   => Carbon::parse(
        $tokenResult->token->expires_at
      )->toDateTimeString(),
    ]);
  }

  /**
   * Logout user (Revoke the token)
   *
   * @return [string] message
   */
  public function logout(Request $request)
  {
    $request->user()->token()->revoke();

    return response()->json([
      'message' => 'Successfully logged out',
    ]);
  }

  /**
   * Get the authenticated User
   *
   * @return [json] user object
   */
  public function hairdresser()
  {
    $query       = Auth::user();
    $hairdresser = new HairdresserResource($query);
    $body        = compact('hairdresser');

    return response()->json(compact('body'));
  }
}

_Marco_'s avatar

My Hairdresser Model:


<?php

namespace App\Models\Hairdressers;

use App\Models\Shops\Shop;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
use Spatie\Permission\Traits\HasRoles;

class Hairdresser extends Authenticatable
{
  use HasApiTokens, Notifiable;
  use HasRoles;

  protected $table      = 'users';
  protected $guard_name = 'web2';

  /**
   * The attributes that are mass assignable.
   *
   * @var array
   */
  protected $fillable = [
    'name',
    'surname',
    'mobile',
    'image',
    'email',
    'password',
  ];

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

  /**
   * The attributes that should be cast to native types.
   *
   * @var array
   */
  protected $casts = [
    'email_verified_at' => 'datetime',
  ];

  public function shop()
  {
    return $this->hasOne(Shop::class, 'user_id');
  }
}

MichalOravec's avatar

Ok in login method change these lines

if (! Auth::attempt($credentials)) {
    return response()->json([
        'message' => 'Unauthorized',
    ], 401);
}

to these

if (! Auth::guard('web2')->attempt($credentials)) {
    return response()->json([
        'message' => 'Unauthorized',
    ], 401);
}

In your HairdresserController

1 like
_Marco_'s avatar

Not really working because that line is just to check if that record exists in db

if (!Auth::attempt($credentials)) {
      return response()->json([
        'message' => 'Unauthorized',
      ], 401);
    }
_Marco_'s avatar

I get a Call to a member function createToken() on null error because probably when I register is still taking the guard as web instead of web2 despite that in my model I have $guard_name = 'web2';

I am just confused...

MichalOravec's avatar

Actually I don't know why you just don't use default Laravel LoginController and just make it for both of your types of users and change just a little bit logic.

_Marco_'s avatar

Just did some debugging and if I do a dd(Auth::user()) in login method I get $guard_name = 'web2';

but if I do dd(Auth::user) in hairdresser() method I get web instead...

very strange.

_Marco_'s avatar

At the end the problem was in the middleware in the route because I had api and was using web but thanks anyway @michaloravec

Please or to participate in this conversation.