Laravel Multiple Guard Authentication Failure

6.7k Views Asked by At

I am creating a Laravel Website with multiple guards. My Goal is to be able to login as Admin, Employee and User using session and User-API using passport. Everything worked fine. However, I wasn't able to Login as Employee.

Here is the git repo. Please check employee branch and database seeders. Git Repo

I will share the steps and code here to find where the problem is:

  1. I created the necessary guards, providers and password brokers
  2. I updated the Employee model
  3. I updated the RedirectIfAuthenticated and Authenticate middlewares
  4. I created the necessary routes and controllers

Here is all this code:

Here is config/auth.php file which I have 4 guards web, api, admin, and employee with their providers and password brokers:

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option controls the default authentication "guard" and password
    | reset options for your application. You may change these defaults
    | as required, but they're a perfect start for most applications.
    |
    */

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

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    |
    | Next, you may define every authentication guard for your application.
    | Of course, a great default configuration has been defined for you
    | here which uses session storage and the Eloquent user provider.
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | Supported: "session", "token"
    |
    */

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

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

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

        'employee' => [
            'driver' => 'session',
            'provider' => 'employees',
        ],

    ],

    /*
    |--------------------------------------------------------------------------
    | User Providers
    |--------------------------------------------------------------------------
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | If you have multiple user tables or models you may configure multiple
    | sources which represent each model / table. These sources may then
    | be assigned to any extra authentication guards you have defined.
    |
    | Supported: "database", "eloquent"
    |
    */

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

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

        'employees' => [
            'driver' => 'eloquent',
            'model' => App\Employee::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    |
    | You may specify multiple password reset configurations if you have more
    | than one user table or model in the application and you want to have
    | separate password reset settings based on the specific user types.
    |
    | The expire time is the number of minutes that the reset token should be
    | considered valid. This security feature keeps tokens short-lived so
    | they have less time to be guessed. You may change this as needed.
    |
    */

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

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

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

    /*
    |--------------------------------------------------------------------------
    | Password Confirmation Timeout
    |--------------------------------------------------------------------------
    |
    | Here you may define the amount of seconds before a password confirmation
    | times out and the user is prompted to re-enter their password via the
    | confirmation screen. By default, the timeout lasts for three hours.
    |
    */

    'password_timeout' => 10800,

];

Here is the Employee.php Model:

<?php

namespace App;

use App\Traits\Permissions\HasPermissionsTrait;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use App\Notifications\Employee\ResetPasswordNotification as EmployeeResetPasswordNotification;

class Employee extends Authenticatable
{
    use Notifiable, HasPermissionsTrait;

    protected $guard = 'employee';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', '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',
    ];

    /**
     * Send the password reset notification.
     *
     * @param  string  $token
     * @return void
     */
    public function sendPasswordResetNotification($token)
    {
        $this->notify(new EmployeeResetPasswordNotification($token));
    }
}

This is the RedirectIfAuthenticated.php class in App\Http\Middleware:

<?php

namespace App\Http\Middleware;

use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Support\Facades\Auth;

class RedirectIfAuthenticated
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string|null  $guard
     * @return mixed
     */
    public function handle($request, Closure $next, $guard = null)
    {
        if ($guard == "admin" && Auth::guard($guard)->check()) {
            return redirect(RouteServiceProvider::ADMINHOME);
        }

        if ($guard == "employee" && Auth::guard($guard)->check()) {
            return redirect(RouteServiceProvider::EMPLOYEEHOME);
        }

        if (Auth::guard($guard)->check()) {
            return redirect('/home');
        }

        return $next($request);
    }
}

Here is Authenticate.php class in App\Http\Middleware:

<?php

namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware;

class Authenticate extends Middleware
{
    /**
     * Get the path the user should be redirected to when they are not authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string|null
     */
    protected function redirectTo($request)
    {
        if ($request->expectsJson()) {
            return response()->json(['error' => 'Unauthenticated.'], 401);
        }

        if ($request->is('admin') || $request->is('admin/*')) {
            return route('admin.login');
        }

        if ($request->is('employee') || $request->is('employee/*')) {
            return route('employee.login');
        }

        if (! $request->expectsJson()) {
            return route('login');
        }
    }
}

Here are the Employee Routes:

Route::prefix('employee')->group(function() {
    Route::get('/', 'Employee\HomeController@index')->name('employee.dashboard');
    Route::get('/home', 'Employee\HomeController@index')->name('employee.home');

    // Login Logout Routes
    Route::get('/login', 'Auth\Employee\LoginController@showLoginForm')->name('employee.login');
    Route::post('/login', 'Auth\Employee\LoginController@login')->name('employee.login.submit');
    Route::post('/logout', 'Auth\Employee\LoginController@logout')->name('employee.logout');

    // Password Resets Routes
    Route::post('password/email', 'Auth\Employee\ForgotPasswordController@sendResetLinkEmail')->name('employee.password.email');
    Route::get('password/reset', 'Auth\Employee\ForgotPasswordController@showLinkRequestForm')->name('employee.password.request');
    Route::post('password/reset', 'Auth\Employee\ResetPasswordController@reset')->name('employee.password.update');
    Route::get('/password/reset/{token}', 'Auth\Employee\ResetPasswordController@showResetForm')->name('employee.password.reset');
});

And Finally here is the App\Http\Controllers\Auth\Employee\LoginController.php:

<?php

namespace App\Http\Controllers\Auth\Employee;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating employees for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = RouteServiceProvider::EMPLOYEEHOME;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest:employee')->except('logout');
    }

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

    public function showLoginForm()
    {
        return view('auth.employee-login');
    }

    /**
     * Handle a login request to the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse
     *
     */
    public function login(Request $request)
    {
        $this->validate($request, [
            'email'   => 'required|email',
            'password' => 'required|min:6'
        ]);

        $credentials = ['email' => $request->email, 'password' => $request->password];
        $remember_token = $request->get('remember');

        if ($res = $this->guard()->attempt($credentials, $remember_token)) {
                return redirect()->intended('/employee/home');
        }

        return back()->withInput($request->only('email', 'remember'));
    }

    public function logout()
    {
        Auth::guard('employee')->logout();
        return redirect('/');
    }
}

THE PROBLEM IS:

In the login function the attempt function is returning true but the redirect is returning me back to the login page, which means the Employee/HomeController.php which has the middleware auth:employee in its constructor is kicking me out and returning me to Authenticate middleware which in turns returns me to the employee login page.

I Checked:

if ($res = $this->guard()->attempt($credentials, $remember_token)) {
    return redirect()->intended('/employee/home');
}

In this if statement in the LoginController the following:

-- dd(Auth::guard('employee')->check()); It returned true.

-- dd(Auth::guard('employee')->user()); It returned:

App\Employee {#379 ▼
  #guard: "employee"
  #fillable: array:3 [▶]
  #hidden: array:2 [▶]
  #casts: array:1 [▶]
  #connection: "mysql"
  #table: "employees"
  #primaryKey: "id"
  #keyType: "int"
  +incrementing: true
  #with: []
  #withCount: []
  #perPage: 15
  +exists: true
  +wasRecentlyCreated: false
  #attributes: array:7 [▶]
  #original: array:7 [▼
    "name" => "employee"
    "email" => "[email protected]"
    "email_verified_at" => "2020-05-05 18:16:25"
    "password" => "$2y$10$15zFxGvAA2GVRkcAYFEXc.3WyOtcdlARlOMwIdSEqbU2.95NNWUJG"
    "remember_token" => null
    "created_at" => "2020-05-05 18:16:25"
    "updated_at" => "2020-05-05 18:16:25"
  ]
  #changes: []
  #classCastCache: []
  #dates: []
  #dateFormat: null
  #appends: []
  #dispatchesEvents: []
  #observables: []
  #relations: []
  #touches: []
  +timestamps: true
  #visible: []
  #guarded: array:1 [▶]
  #rememberTokenName: "remember_token"

I still can't find where the problem is.. Any help. Thank you.

1

There are 1 best solutions below

1
On BEST ANSWER

The problem was not in the LoginController or the logic in the question. Everything was right. However the employee migration were wrong it didn't have an id in the table and that was the fix.. So this question is irrelevant.