Saturday, July 27

How to implement multiple user authentications using Laravel Guards

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 – weband apiwith 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 databaseprovider deals with retrieving users credentials directly from the database, while the eloquentprovider 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 theAdminModel 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 Usermodel 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 providercalled adminswhich 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' indicates eloquent is the abstraction layer we’ll use for communicating with our database.
  • 'model' => AppAdmin::class basically binds the model key to the admin model we created earlier on. The final look for the config/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/viewsdirectory, 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 the resources/views directory, create an admin.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 the admin.

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

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x