Authenticate Users from Multiple Tables in Laravel
Jan 14, 2021
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