How to implement multiple authentication in Laravel
Tags: laravel, php
Published: 2024-03-19 09:38:37

Building a flexible multiple authentication system in Laravel is a crucial task for many web applications. This system allows different types of users, such as administrators and regular users, to log in through different authentication processes. This not only enhances the security of the application but also provides a more personalized user experience.

In Laravel, this can be achieved by leveraging its powerful and extensible authentication system. However, setting up multiple authentication can be a bit tricky as it involves several steps including installing Laravel, creating authentication scaffolding, defining user models, updating migration files, creating auth controllers, defining routes, creating views, updating middleware, and running migrations.

In the following guide, we will walk through each of these steps in detail, providing code examples and explanations to help you understand and implement a flexible multiple authentication system in your Laravel application.

## Getting started

### Install Laravel

First, you need to install Laravel using Composer. Open your terminal and run the following command:

```
composer create-project --prefer-dist laravel/laravel projectName
```
Replace projectName with the name you want to give to your project.

### Install Laravel Breeze
Laravel Breeze is a simple authentication scaffolding built on Blade and Tailwind. You can install it using the following commands:

```
cd projectName
composer require laravel/breeze --dev
php artisan breeze:install
```
These commands will install the authentication controllers, views, and routes.
After running the `breeze:install` command, you'll need to compile your assets using NPM:

```
npm install
npm run dev
```
Finally, run the migrations with `php artisan migrate`.

### Start the Laravel Development Server
You can start your Laravel development server using the `php artisan serve` command and visit `http://localhost:8000` in your web browser to see your application.

## Setting Up Multiple Authentication
### Modify `.env` file
You need to add the following lines to your `.env` file to set up multiple domains for each authentication type:

```php
APP_ADMIN_DOMAIN=http://localhost
APP_ADMIN_PREFIX=/admin


APP_USER_DOMAIN=http://localhost
APP_USER_PREFIX=/
```
### Add Route Files
You need to create two separate route files for admin and user:

`routes/admin.php`
```php
Route::domain(env('APP_ADMIN_DOMAIN'))
    ->prefix(env('APP_ADMIN_PREFIX'))
    ->namespace('Admin')->group(function () {
        Route::as('admin.')->group(function () {
            Auth::routes([
                'register' => false,
                'reset' => false,
                'verify' => false,
            ]);

            Route::middleware('auth:admin')->group(function () {
                Route::get('/home', function () {
    return view('admin.home');
})->name('admin.home');
            });
        });
    });
```

`routes/user.php`
```php
Route::domain(env('APP_USER_DOMAIN'))
    ->prefix(env('APP_USER_PREFIX'))
    ->as('user.')->namespace('User')->group(function () {
        Auth::routes([
            'register' => true,
            'reset' => true,
            'verify' => true,
        ]);

        Route::middleware('auth:user')->group(function () {
            Route::get('/home', function () {
    return view('user.home');
})->name('user.home');
        });
    });
```
Then you need to register these routes in your `RouteServiceProvider`:

```php
// app/Providers/RouteServiceProvider.php

public function map()
{
    $this->mapApiRoutes();

    $this->mapWebRoutes();

    $this->mapAdminRoutes(); // Add this line

    $this->mapUserRoutes(); // Add this line
}

protected function mapAdminRoutes()
{
    Route::middleware('web')
        ->namespace($this->namespace)
        ->group(base_path('routes/admin.php'));
}

protected function mapUserRoutes()
{
    Route::middleware('web')
        ->namespace($this->namespace)
        ->group(base_path('routes/user.php'));
}
```
This code will register the routes defined in `routes/admin.php` and `routes/user.php` with the Laravel router. The `mapAdminRoutes` and `mapUserRoutes` methods are responsible for this. They tell Laravel to load the routes from these files and apply the web middleware group to them.

Finally you need to update the `Authenticate` and `RedirectIfAuthenticated` middlewares to handle the authentication and redirection based on the user type. 

```php
// app/Http/Middleware/Authenticate.php

namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

class Authenticate extends Middleware
{
    /**
     * Get the path the user should be redirected to when they are not authenticated.
     */
    protected function redirectTo(Request $request): ?string
    {
        if (!$request->expectsJson()) {
            if (Route::is('admin.*')) {
                Auth::shouldUse('admin');
                return route('admin.login');
            } elseif (Route::is('user.*')) {
                Auth::shouldUse('user');
                return route('user.login');
            }
        }

        return null;
    }
}
```

```php
// app/Http/Middleware/RedirectIfAuthenticated.php

namespace App\Http\Middleware;

use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

class RedirectIfAuthenticated
{
    /**
     * Handle an incoming request.
     */
    public function handle(Request $request, Closure $next, string ...$guards)
    {
        $guards = empty($guards) ? [null] : $guards;

        foreach ($guards as $guard) {
            if (Auth::guard($guard)->check()) {
                if (Route::is('admin.*')) {
                    Auth::shouldUse('admin');
                    return redirect(RouteServiceProvider::ADMIN_HOME());
                } elseif (Route::is('user.*')) {
                    Auth::shouldUse('user');
                    return redirect(RouteServiceProvider::USER_HOME());
                }
            }
        }

        return $next($request);
    }
}
```
Then update `app/Providers/RouteServiceProvider.php`: You need to define the `ADMIN_HOME` and `USER_HOME` constants. These constants should return the URLs for the admin and user home pages, respectively. You can use the env function to get these URLs from your `.env` file:

```php
public static function ADMIN_HOME()
{
    self::$admin_home = env('APP_ADMIN_DOMAIN') . env('APP_ADMIN_PREFIX');

    return self::$admin_home;
}

public static function USER_HOME()
{
    self::$user_home = env('APP_USER_DOMAIN') . env('APP_USER_PREFIX');

    return self::$user_home;
}
```

Now, your application should correctly set the authentication guard and redirect the user based on the domain of the incoming request.

### Set Up Authentication Guards and Providers
Guards define how users are authenticated for each request. In your `config/auth.php` file, you need two guards: `user` and `admin`. Each guard uses the session driver and a user provider, which specifies how the users are retrieved from the database. The user providers are also defined in the `config/auth.php` file. Each provider uses the eloquent driver and specifies the model to use. The users provider uses the User model, and the admins provider uses the Admin model.

```php
'guards' => [
    'user' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'admin' => [
        'driver' => 'session',
        'provider' => 'admins',
    ],
],
'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\Models\User::class,
    ],
    'admins' => [
        'driver' => 'eloquent',
        'model' => App\Models\Admin::class,
    ],
],
```

### Setting Up Migration Files
Open your terminal and navigate to your project directory, then run the following commands:

```php
php artisan make:migration create_users_table --create=users
php artisan make:migration create_admins_table --create=admins
```
Open the `create_users_table.php` file and update the up method as follows:

```php
public function up()
{
    Schema::create('users', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('email')->unique();
        $table->timestamp('email_verified_at')->nullable();
        $table->string('password');
        $table->rememberToken();
        $table->timestamps();
    });
}
```
Similarly, open the `create_admins_table.php` file and update the up method as follows:

```php
public function up()
{
    Schema::create('admins', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('email')->unique();
        $table->timestamp('email_verified_at')->nullable();
        $table->string('password');
        $table->rememberToken();
        $table->timestamps();
    });
}
```
These migration files define the structure of your users and admins tables. You can run these migrations using the `php artisan migrate` command. This will create the tables in your database.
### Setup Models and Controllers
#### Models
Create or update file `app/Models/User.php` with the following code:

```php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];
}
```
Also `app/Models/Admin.php`:

```php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class Admin extends Authenticatable
{
    use Notifiable;

    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];
}
```
#### Controllers
Create or update file `app/Http/Controllers/User/Auth/LoginController.php` with the following code:

```php
<?php

namespace App\Http\Controllers\User\Auth;

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

class LoginController extends Controller
{
    use AuthenticatesUsers;

    protected $redirectTo;

    public function __construct()
    {
        $this->middleware('guest')->except('logout');
        $this->redirectTo = RouteServiceProvider::USER_HOME();
    }
}
```
Create a new file `app/Http/Controllers/Admin/Auth/LoginController.php` and add the following code:

```php
<?php

namespace App\Http\Controllers\Admin\Auth;

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

class LoginController extends Controller
{
    use AuthenticatesUsers;

    protected $redirectTo;

    public function __construct()
    {
        $this->middleware('guest:admin')->except('logout');
        $this->redirectTo = RouteServiceProvider::ADMIN_HOME();
    }

    protected function guard()
    {
        return Auth::guard('admin');
    }
}
```
These are the basic files for your Models and Controllers. You can add more methods and properties as per your requirements.

### Setup Views
Since you're using Laravel Breeze, it already provides a good starting point for authentication views. However, you'll need to customize these views for each user type (user and admin).  

#### User Views

For the user views, you can use the existing views provided by Breeze. These are located in the `resources/views/auth` directory. The main views are:  

- `login.blade.php`: This is the login form for users.
- `register.blade.php`: This is the registration form for users.
- `passwords/email.blade.php`: This is the form where users can request a password reset link.
- `passwords/reset.blade.php`: This is the form where users can reset their password.

After login Laravel will redirect user to route `/home` so we need to create a view for that. Create a new file `resources/views/user/home.blade.php` and add the following code:

```php
<!-- resources/views/user/home.blade.php -->

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('User Dashboard') }}</div>

                <div class="card-body">
                    Hello User, {{ Auth::user()->name }}
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
```

You can customize these views as needed for your application.  

#### Admin Views
For the admin views, you'll need to create new views. You can follow the structure of the user views, but place them in a separate directory for admins. For example, you could create a `resources/views/admin/auth` directory and add the following views:  

- `login.blade.php`: This is the login form for admins.
- `passwords/email.blade.php`: This is the form where admins can request a password reset link.
- `passwords/reset.blade.
php`: This is the form where admins can reset their password.

Here's a basic example of what the `login.blade.php` view might look like:

```php
<!-- resources/views/admin/auth/login.blade.php -->

@extends('layouts.app')

@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('admin.login') }}">
                        @csrf

                        <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 @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>

                                @error('email')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </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 @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">

                                @error('password')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <div class="col-md-6 offset-md-4">
                                <div class="form-check">
                                    <input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>

                                    <label class="form-check-label" for="remember">
                                        {{ __('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>

                                @if (Route::has('admin.password.request'))
                                    <a class="btn btn-link" href="{{ route('admin.password.request') }}">
                                        {{ __('Forgot Your Password?') }}
                                    </a>
                                @endif
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
```
For the view `home` after login successfully, create a new file `resources/views/admin/home.blade.php` and add the following code:

```php
<!-- resources/views/admin/home.blade.php -->

@extends('layouts.app')

@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">
                    Hello Admin, {{ Auth::guard('admin')->user()->name }}
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
```
Now, after login, you can redirect the user to their respective home view based on their user type.

## Testing out the application

To test the application, we need to create test accounts for both the user and the admin. Since we don't have a registration functionality, we will use Laravel's Tinker to manually create these accounts. 

First, open a terminal and navigate to your Laravel project directory. Then, start a Tinker session by running the following command:

```
php artisan tinker
```
Once you're in the Tinker session, you can create an admin and a user with the following commands:

```php
// Create an admin
$admin = new \App\Models\Admin;
$admin->email = 'admin@gmail.com';
$admin->name = 'admin';
$admin->password = bcrypt('secret');
$admin->save();

// Create a user
$user = new \App\Models\User;
$user->email = 'user@gmail.com';
$user->name = 'user';
$user->password = bcrypt('secret');
$user->save();
```

After running these commands, you should have an admin with the email `admin@gmail.com` and password `secret`, and a user with the email `user@gmail.com` and password `secret`.

Now, you can test the application by logging in with these accounts.

## Summary
In this article, we have successfully implemented a multiple authentication system in Laravel for two user types: `User` and `Admin`. We have created separate migration files, models, controllers, views, and routes for each user type. We have also updated the `Authenticate` and `RedirectIfAuthenticated` middleware to handle multiple guards and updated the `auth.php` configuration file to include User and Admin guards and providers.

We have also defined separate domains and prefixes for User and Admin in our `.env` file. Furthermore, we have created views for login, home, and password reset for both User and Admin.

However, there are a few things that need to be checked or considered for a complete and robust authentication system. These include implementing the password reset functionality for both User and Admin, implementing the registration functionality if registration is allowed, implementing proper validation for all forms, implementing necessary security measures like hashing passwords, protecting against CSRF attacks, and testing all functionalities thoroughly.

This basic setup can be further enhanced based on the application's requirements, such as adding user roles, permissions, email verification, two-factor authentication, etc.