Multiple authentication in Laravel 9
Tags: laravel, php, security, webdev
Published: 2023-01-16 01:46:52

## Problem
In a basic web app, we usually have two parts, app and cms. App is for user to use and cms for staff to manage content in that system. So to design this web app, we need to have a role. User with role admin can access cms site and user without role admin cannot access cms. But what if you want to use 2 difference models for security, for performance? How can we authenticate with 2 difference models?
## Introduce app
This is a web app have two parts, app and CMS. Model Staff can only access CMS part and model User can only access app part. Since Laravel have default guard authentication for User model, we will discuss more about Staff model
## Configure guard
First of all, you need to add new guard to existing guard in `config/auth.php`. 

```
   'guards' => [
        ...
        'staff' => [
            'driver' => 'session',
            'provider' => 'staffs',
        ],
    ],
```

In this configuration, I add new guard name **staff**, this guard use driver is session and provider name staffs. Currently, we do not have any provider name staffs, so let's define it.

```
    'providers' => [
        ...
        'staffs' => [
            'driver' => 'eloquent',
            'model' => App\Models\Staff::class,
        ],
    ],
```
With this definition, we define provider name staffs using driver eloquent and model we want to use is `App\Models\Staff`. Last but not least, we need to configure password part so we can reset password with Staff model.

```
    'passwords' => [
        ...
        'staffs' => [
            'provider' => 'staffs',
            'table' => 'staff_password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],
```
This is quite simple. We use provider name staffs, table is staff_password_resets, expire and throttle is 60 minutes. Since we use staff_password_resets to reset password then remember to migrate it. 
## Ensure
After configuration, we need to ensure these things:

- Migrate table for reset password
It will look like this one:

```
public function up()
    {
        Schema::create('staff_password_resets', function (Blueprint $table) {
            $table->string('email')->index();
            $table->string('token');
            $table->timestamp('created_at')->nullable();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('staff_password_resets');
    }
```

- Have Staff model
```
<?php

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class Staff extends Authenticatable
{
    use HasApiTo   'guards' => [
        ...
        'staff' => [
            'driver' => 'session',
            'provider' => 'staffs',
        ],
    ],kens, HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}
```

- Have data in table

![Data in Staff table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1zjpgyqtmm1j017f0uda.png)

## Use new guard
Let's try out new guard. In login logic, you will have code like this:
`
Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))
`
This one will not work because it will use default guard which is user guard. If we want to use new guard, we need to explicitly use it. Change it to this:
`
Auth::guard('staff)->attempt($this->only('email', 'password'), $this->boolean('remember'))
`
So this one tell Laravel to use guard name staff instead of default. Try to login and check the session, you will see that we login successfully and have a session. However, we cannot go to page which ask for authentication. Why? Cause in these pages, we still not use new guard yet. This is my route:
```
Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
```
Take a look at `middleware(['auth', 'verified'])`. It will go to auth and verified middlewar before run callback in Route::get. Verified middleware simply check verified_at colume in table is null or not and auth middleware check that user is authenticated or not. Cause we just use auth middleware then again, it use default guard which is user guard. We need to tell it to use staff guard by passing parameter to middleware. Change it to this one:
 ```
Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware(['auth:staff', 'verified'])->name('dashboard');
```
As you can see, we change to use auth middleware with staff guard instead of user guard. Then try to login again and you can access `/dashboard` route:

![Dashboard after login](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pxmii0wuj8moegbrpglv.png)
Perfection. You rock.
## Testing
Lastly, we need to test to make sure that user in User table cannot use their data to login to `/dashboard`. This is user in User table:

![User in User table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kpb117vgndts953k93ln.png)
Try to login using this credential:

![Try to login with User table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n6gioim0buecb9gl3exe.png)
Failed. Great, this is what we expect, it cannot find any records cause when using staff guard, it just only take a look in Staff table. That's why, it did not found any records. That's all. Thanks for reading.