Introduction
As developers, there are couple of times where your application will require different type of users. Take for instance you’re building a Hospital Management software and you need both Doctors and Patients to be entirely unique users. How do you handle registration and authentication for both users in such a scenario? Laravel makes it easy to authenticate against multiple user models by using guards and providers.
Guards and Providers
Before we dive into building our app, let’s understand the concepts behind Guards and Providers which are essentially the building blocks of implementing multiple user authentication in Laravel.
What are Guards?
Guards can be seen as a way of identifying authenticated users. Laravel ships with two default guards – web
and api
with provision to add as many custom guards as possible. The web
guard uses sessions to ensure users are authenticated, while the api guard uses a token to check if a user is authenicated by ensuring a valid token is present on every request.
What are Providers?
Providers defines how users are actually retrieved out of your database or any other storage mechanism your application uses to store and persist data. By default, laravel ships with two providers –database
and eloquent.
The database
provider deals with retrieving users credentials directly from the database, while the eloquent
provider is an abstraction layer for working with our database.
Now that we have an idea of what guards and providers are, let’s put them into practise by building our app so that we can fully grasp both concepts.
Getting Started
We’ll be building a simple multi auth system that takes into account two different types of users – Users
and Admins
Let’s get started by creating a new laravel project. Using the laravel installer, run the following command:
laravel new multi-auth
This will install a new laravel project for you in the multi-auth
directory which will be the name of our project for this tutorial.
For our default Users
, we’ll be using laravel’s inbuilt auth system to handle registration and authentication. Run the following command:
php artisan make:auth
This will create a default auth scaffolding for our project covering everything that has to do with registration, views and routes for the main Users
Admin Migration and Model
Next, we’ll be creating the model, migration and controller for our Admin
Users.
Run
php artisan make:model Admin -m -c
This will create a migration and controller file associated with theAdmin
Model for us.
Tip: The –m and –c flags are automatically used to generate a migration and controller file respectively
Theadmin
migration will take a similar structure to the users
migration that comes bundled with laravel by default. Head over to the database/migrations
directory and edit the up
method of the admin
migration with the following code:
public function up()
{
Schema::create('admins', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
Before we run our migrations, edit the .env
file with your database configurations and then run the following command:
php artisan migrate
We will also edit the Admin
model to look a lot like the User
model laravel already provides for us. This is done so that the Admin
model can have access to some of the underlying features laravel’s default authentication system provides. Edit AppAdmin.php
with the following code:
<?php
namespace App;
use IlluminateNotificationsNotifiable;
use IlluminateFoundationAuthUser as Authenticatable;
class Admin extends Authenticatable
{
use Notifiable;
protected $fillable = ['name', 'email', 'password'];
protected $hidden = [
'password', 'remember_token',
];
}
If you take a look at the Admin
model, you’ll notice it’s a bit different from the traditional model. IlluminateFoundationAuthUser as Authenticatable
is just a class that tacks on some of the additional interfaces
and traits
the auth system needs to work. The underlying class IlluminateFoundationAuthUser
already extends model
which gives us all the the relational functionality you would typically expect if you were to extend the model
The $fillable
array contains the attributes
we want on our model
to be mass assignable, while the $hidden
array contains the attributes
we want hidden from our model
array.
Creating the Admin guard
To create our admin guard, head over to the config/auth.php
file. You’ll notice a guards
array has already been predefined with a web
and api
guard, which we already stated Laravel comes with. Edit the guards
array with the following code:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
'admin'=> [
'driver'=>'session',
'provider'=>'admins'
]
],
We added our admin
guard and directed it to use a provider
called admins
which we shall be creating shortly.
Still within the config/auth.php
, edit the providers
array, so that it’s new structure looks like the code below:
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => AppUser::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => AppAdmin::class,
],
],
Here, we created a new admins
provider.
'driver' => 'eloquent'
indicateseloquent
is the abstraction layer we’ll use for communicating with our database.'model' => AppAdmin::class
basically binds themodel
key to theadmin
model we created earlier on. The final look for theconfig/auth.php
will be this:
<?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' => 'token',
'provider' => 'users',
],
'admin'=> [
'driver'=>'session',
'provider'=>'admins'
]
],
/_
|--------------------------------------------------------------------------
| 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' => AppUser::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => AppAdmin::class,
],
],
/_
|--------------------------------------------------------------------------
| 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,
],
'admins' => [
'provider' => 'admins',
'table' => 'password_resets',
'expire' => 60,
],
],
];
Admin Views
We’ll be needing a login
and register
view for our Admin
users. These views will be similar to the views the default auth
scaffolding provides. Before we create those views, let’s create a base layout for those views to extend.
In the resources/views/layouts
directory, create an admin.blade.php
file and add the following code to it:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel Guards') }}</title>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
<!-- Fonts -->
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Raleway:300,400,600" rel="stylesheet" type="text/css">
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
<div id="app">
<nav class="navbar navbar-expand-md navbar-light navbar-laravel">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ _**('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav mr-auto">
</ul>
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ml-auto">
<!-- Authentication Links -->
@if(!(Auth('admin')->user()))
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ **_('Login') }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ _('Register') }}</a>
</li>
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>Admin <span class="caret"></span>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="/login/admin">Login</a>
<a class="dropdown-item" href="/register/admin">Register</a>
</div>
</li>
@else
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth('admin')->user()->name }} <span class="caret"></span>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('logout.admin') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ _('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout.admin') }}" method="POST" style="display: none;">
@csrf
</form>
</div>
</li>
@endif
</ul>
</div>
</div>
</nav>
<main class="py-4">
@yield('content')
</main>
</div>
</body>
</html>
In the resources/views
directory, create an admin
folder. Within the admin folder you just created, create two new files register.blade.php
and login.blade.php
. Edit the register.blade.php
with the following code:
@extends('layouts.admin')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ **('Register') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('register.admin') }}" aria-label="{{ **('Register') }}">
@csrf
<div class="form-group row">
<label for="name" class="col-md-4 col-form-label text-md-right">{{ **('Name') }}</label>
<div class="col-md-6">
<input id="name" type="text" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" name="name" value="{{ old('name') }}" required autofocus>
@if ($errors->has('name'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('name') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ **('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required>
@if ($errors->has('email'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ **('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required>
@if ($errors->has('password'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group row">
<label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ **('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Register') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
Add the following code to the login.blade.php
file
@extends('layouts.admin')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Admin Login</div>
<div class="card-body">
<form method="POST" action="{{ route('login.admin') }}" aria-label="{{ **('Login') }}">
@csrf
<div class="form-group row">
<label for="email" class="col-sm-4 col-form-label text-md-right">{{ **('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required autofocus>
@if ($errors->has('email'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ **('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required>
@if ($errors->has('password'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group row">
<div class="col-md-6 offset-md-4">
<div class="checkbox">
<label>
<input type="checkbox" name="remember" {{ old('remember') ? 'checked' : '' }}> {{ **('Remember Me') }}
</label>
</div>
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ **('Login') }}
</button>
<a class="btn btn-link" href="{{ route('password.request') }}">
{{ **('Forgot Your Password?') }}
</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
Next, we need to include a login and register link for our Admin
users. Edit the layouts/app.blade.php
file with the following code:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel Guards') }}</title>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
<!-- Fonts -->
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Raleway:300,400,600" rel="stylesheet" type="text/css">
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
<div id="app">
<nav class="navbar navbar-expand-md navbar-light navbar-laravel">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="{{ _**('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav mr-auto">
</ul>
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ml-auto">
<!-- Authentication Links -->
@guest
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ **_('Login') }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ _('Register') }}</a>
</li>
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false" v-pre>
Admin
<span class="caret"></span>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="/login/admin">Login</a>
<a class="dropdown-item" href="/register/admin">Register</a>
</div>
</li>
@else
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false" v-pre>
{{ Auth::user()->name }}
<span class="caret"></span>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('logout') }}" onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ _('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
@csrf
</form>
</div>
</li>
@endguest
</ul>
</div>
</div>
</nav>
<main class="py-4">
@yield('content')
</main>
</div>
</body>
</html>
Next, run the following command php artisan serve
and then visit http://localhost:8000/login
in your browser.
You should see the following page
Admin controller and routes
Remember we created our AdminController earlier, so head over to appHttpControllersAdminController.php
file and add the following code:
<?php
namespace AppHttpControllers;
use IlluminateHttpRequest;
use AppAdmin;
use Auth;
class AdminController extends Controller
{
public function create()
{
return view('admin.register');
}
public function registerAdmin(Request $request)
{
$this->validate($request, [
'email'=> 'required|unique:users|email|max:255',
'name'=> 'required',
'password'=> 'required|min:6|confirmed'
]);
Admin::create([
'name'=>$request->name,
'email'=>$request->email,
'password'=>bcrypt($request->password)
]);
return redirect()->intended('/home/admin');
}
public function loginAdmin()
{
return view('admin.login');
}
public function adminAuth(Request $request)
{
$this->validate($request, [
'email'=>'required|email',
'password'=>'required'
]);
$email = $request->email;
$password = $request->password;
$remember = $request->remember;
if(Auth::guard('admin')->attempt(['email'=> $email, 'password'=> $password], $remember)){
return redirect()->intended('/home/admin');
} else {
return redirect()->back()->with('warning', 'Invalid Email or Password');
}
}
public function home()
{
return view('admin');
}
public function logout()
{
Auth::guard('admin')->logout();
return redirect('/login/admin');
}
}
- The
create()
method returns the view for registering a new admin. - The
registerAdmin()
method handles creating a new admin on the database and then returning the dashboard view for our admin once an account has been successfully created. - The
adminAuth()
method authenticates a registered admin using the admin guard. Let’s take a look at the following block of code to understand better.
if(Auth::guard('admin')->attempt(['email'=> $email, 'password'=> $password], $remember)){
return redirect()->intended('/home/admin');
} else {
return redirect()->back()->with('warning', 'Invalid Email or Password');
}
}
Using the auth
facade, we specify the admin
guard as the instance we would like to use to manage our authentication in this case. The attempt
method returns true
if the authentication was successful and false
if it wasn’t.
- The
home()
method returns the admin dashboard view which we haven’t created yet. In theresources/views
directory, create anadmin.blade.php
file and add the following code to the file:
@extends('layouts.admin')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Admin Dashboard</div>
<div class="card-body">
<p>You're Logged in as an Admin </p>
</div>
</div>
</div>
</div>
</div>
@endsection
- The
logout()
method successfully logouts theadmin
.
Next, let’s add our routes
. Edit the routes/web.php
file with the following code:
Route::get('/', function () {
return view('welcome');
});
//generated by laravel's default auth
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
//admin routes
Route::get('/register/admin', 'AdminController@create');
Route::get('/login/admin', 'AdminController@loginAdmin');
Route::post('/register/admin', 'AdminController@registerAdmin')->name('register.admin');
Route::post('/login/admin', 'AdminController@adminAuth')->name('login.admin');
//admin routes we want protected
Route::group(['middleware'=>'auth:admins'], function(){
Route::get('/home/admin', 'AdminController@home');
Route::post('/logout/admin', 'AdminController@logout')->name('logout.admin');
});
Next, try creating a normal user
and admin
account and then logging in. You should see the following pages once the authentication
has been successful.
Conclusion
In this tutorial, we’ve seen how to create a simple multi user authentication system in Laravel using guards and providers.
The codebase for this tutorial can be found on Github
Source: Scotch.io