fareez.info

Authenticate Users from Multiple Tables in Laravel

It is a common practice to store different user types in different database tables. For example, in most applications you will have an Admin user and a normal user. Though we can have all the users in a single table and discriminate them based on a column, sometimes if the fields are quite different, we would like to have different table for each type of user.

When you scaffold a new Laravel project, you will have a User model with migration and default authentication configuration. But when you need to authenticate users from multiple tables, Laravel has Authentication Guards to handle that.

Let’s create a new Laravel app and see how to handle the situation.

composer create-project laravel/laravel multiguard

Make sure you create the database and configure it with the app.

We have User model and migration created by default. Now we are going to create Admin model and migration for storing and authenticating administrators.

php artisan make:model Admin -m

Now we have to make this model authenticatable, so that we can use it in defining the guard. So inherit the Admin model from Illuminate\Foundation\Auth\User instead of Illuminate\Database\Eloquent\Model.

Also in the migrations file for admin, add all the fields mentioned in the users table. We may not be needing all the fields but for simplicity we will just add all those.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;

class Admin extends Authenticatable
{
    use HasFactory;

    protected $guarded = [];
}

Now go to config/auth.php and add an additional provider for Admin model in the providers array as shown below

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

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

],

In the same file there is another array named guards, we have to update that to add a new guard using our newly created provider.

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

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

Now the wiring is done and the new authentication guard is ready for use. Let’s create simple login and logout routes and controllers for both type of users separately.

php artisan make:controller UserAuthController
php artisan make:controller AdminAuthController
Route::get('/', [UserAuthController::class, 'index'])
    ->name('user.home');
Route::get('/login', [UserAuthController::class, 'login'])
    ->name('user.login');
Route::post('/login', [UserAuthController::class, 'handleLogin'])
    ->name('user.handleLogin');
Route::get('/logout', [UserAuthController::class, 'index'])
    ->name('user.logout');


Route::get('admin/', [AdminAuthController::class, 'index'])
    ->name('admin.home');
Route::get('admin/login', [AdminAuthController::class, 'login'])
    ->name('admin.login');
Route::post('admin/login', [AdminAuthController::class, 'handleLogin'])
    ->name('admin.handleLogin');
Route::get('admin/logout', [AdminAuthController::class, 'index'])
    ->name('admin.logout');

Now let’s fill the UserAuthController with the following functions so that it can handle the routes as we expect.

class UserAuthController extends Controller
{
    public function index()
    {
        return view('user.home');
    }

    public function login()
    {
        return view('user.login');
    }

    public function handleLogin(Request $req)
    {
        if(Auth::attempt(
            $req->only(['email', 'password'])
        ))
        {
            return redirect()->intended('/');
        }

        return redirect()
            ->back()
            ->with('error', 'Invalid Credentials');
    }

    public function logout()
    {
        Auth::logout();

        return redirect()
            ->route('user.login');
    }

}

For brevity, I haven’t added the code related to the views here. You can find that in the github repo.

Here if we notice we are authenticating users with Auth::attempt and logging out using Auth::logout. We have two guards now named web and webadmin configured in the config\app.php. If you notice web is mentioned as the default guard in the same file. So the above code of Auth::attempt & Auth::logout will use the default guard.

AdminAuthController should be filled with similar logic but it is supposed to use webadmin guard. So copy all the functions as it is from UserAuthController and replace Auth::attempt & Auth::logout with Auth::guard('webadmin')->attempt & Auth::guard('webadmin')->logout. Also update the route names from user. to admin. wherever we have used a route name.

class AdminAuthController extends Controller
{
    public function index()
    {
        return view('admin.home');
    }

    public function login()
    {
        return view('admin.login');
    }

    public function handleLogin(Request $req)
    {
        if(Auth::guard('webadmin')
               ->attempt($req->only(['email', 'password'])))
        {
            return redirect()
                ->route('admin.home');
        }

        return redirect()
            ->back()
            ->with('error', 'Invalid Credentials');
    }

    public function logout()
    {
        Auth::guard('webadmin')
            ->logout();

        return redirect()
            ->route('admin.login');
    }
}

Now we have to guard the home routes for admin and user. So go back to the web.php and update those two routes with the auth middleware.

Route::get('/', [UserAuthController::class, 'index'])
    ->name('user.home')
    ->middleware('auth:web');

Route::get('admin/', [AdminAuthController::class, 'index'])
    ->name('admin.home')
    ->middleware('auth:webadmin');

This is supposed to redirect an user to the login page if the user is not already logged in. So to specify which route we have to redirect the user to, we have to update the Authenticate middleware.

If someone is trying to access ‘/’ he should be redirected to user login page. In the case of ‘/admin’, he should be redirected to the admin login page.

Update the redirectTo method with the following code to acheive this.

protected function redirectTo($request)
{
    if (! $request->expectsJson()) {
        if ($request->routeIs('admin.*')) {
            return route('admin.login');
        }

        return route('user.login');
    }
}

This is feasible only because of the naming convention we followed in defining the routes. We defined all admin routes with 'admin.' prefix and and all user routes with 'user.' prefix.

We can do the same with any number of database tables and models. If required, Laravel allows us to create custom Authentication Guards

You can find the code at this repo

comments powered by Disqus