Laravel 5.4 Native Multi Authentication Notes

For this we will demonstrate creating a customer type with their own separate authentication table, model, login and registration.
This shows in internal account creation whereby only the admin is allow to register new users of this type. This might be useful for scenarios where there are many user types (each with fundamentally different models) that an administrator is allowed to create users for.

Note: In a lot of cases you may well be over-engineering, consider first whether to actually need multi auth. Could you just add a 'user_type' field to your tables for example. Where that starts to fall down as a solution is if your user types have entirely different models and the separation of the user types can make it easier to separate these concerns into individual models of those users along with authentication.

Create a placeholder login url

e.g. http://localhost/customer/login

Create a login form

Laravel ships with a default login and registration form, along with authentication controllers. We can use these as starting code for our own logins.

Navigate to /var/www/resources/views/auth. This is where you'll find the default single user type login and registration views/templates.

Copy login.blade.php to customer-login.blade.php to begin creating your customer login form. Customise as required.

Make sure you edit the 'POST' action of your login form to post to the correct login controller. By default this will be /login but we need it to go to the customer login route.

Edit the POST action of your login for to action={{ route('customer.login.submit') }}

If you're wondering where the route customer.login.submit comes from, we make it by creating a named route in our CustomerContoller later on.

Create a new Laravel login controller

Navigate to /var/www/app/Http/Controllers/Auth

Copy the standard user login controller as a template for your customer login controller:

cp LoginController.php CustomerLoginController.php

Be sure to now update the CustomerLoginController class:

  • Name the class as CustomerLoginController
  • Replace the Auth guard check to: Auth::guard('**customer**')
  • Change login sucess redirect to your customer dashboard route: customer.dashboard
namespace App\Http\Controllers\Auth;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Auth;

class CustomerLoginController extends Controller
{
    public function __construct()
    {
        $this->middleware('guest');
    }

    public function username() {
      return 'username';
    }

    public function showLoginForm()
    {
        return view('auth.customer-login');
    }

    public function login(Request $request)
    {
        // Validate the form data
  $this->validate($request, [
      'password' => 'required|min:6',
      'username' => 'required',
  ]);
  // Attempt login
  if ( Auth::guard('customer')->attempt(['username' => $request->username, 'password' => $request->password], $request->remember)) {
      return redirect()->intended(route('customer.dashboard'));
  } else {
  //Else redirect to form login
     return redirect()->back()->withInput($request->only('username', 'remember'));
  }
    }

}

Above: CustomerLoginController.php in /var/www/app/Http/Controllers/Auth

Create a login route

The url for customer logins will be: 'localhost/customer/login' so we need to add a route for that.

Edit you /var/www/routes/web.php routes file to include a route group for the 'customer' section:

//Customer Area
Route::prefix('customer')->group(function()
{
  Route::get('/login', 'Auth\[email protected]')->name('customer.login');
  Route::post('/login', 'Auth\[email protected]')->name('customer.login.submit');
  Route::get('/', [email protected]')->name('customer.dashboard');
});

You should now beable to navigate to 'localhost/customer/login' and see you new shiny login page. It won't work yet, though, as a guard needs to be created for the 'customer' users.

If you try logging in now with random credentials, you'll receive the error:

"InvalidArgumentException in AuthManager.php line 86 Auth guard [customer] is not defined: Laravel Auth guard [customer] is not defined.

We resolve this by defining a custom guard will be used to authenticate our custom user type 'customer' and authenticate against its own table.

Create a Laravel custom guard

Navigate to /var/www/config and edit the auth.php file. Add 'customer' to the guards array beneath the default 'web' guard:

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'customer' => [
        'driver' => 'session',
        'provider' => 'customer',
    ],
],

Next, define the provider for the the 'customer' guard. In the 'providers' array, ass 'customer' with the 'eloquent' driver and your associated customer model:

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\User::class,
    ],

    'customer' => [
        'driver' => 'eloquent',
        'model' => App\Customer::class,
    ],
],

These notes assume you have a 'Customer' class in your /app directory. Note: In order to be able to authenticate against this table, the following fields must be present on the table for that model:

  • email - e.g. varchar(255)
  • password e.g. varchar(255)
  • remember_token e.g. varchar(100)
  • timestamps
  • last_accessed e.g. datetime
  • username - If using username for authentication as these notes do.

Consult the 2014_10_12_000000_create_users_table.php migration for an example of creating a table with these field, note you'll probably need to perform an edit migratoin rather than a create. e.g. php artisan make:migration --table=customers 'add login fields to customers table'

Example: Edit an existing 'customers' table to include fields for authentication.

public function up()
{
    Schema::table('customers', function (Blueprint $table) {
        $table->string('username');
        $table->string('email')->unique();
        $table->string('password');
        $table->rememberToken();
        $table->timestamps();
        $table->datetime('last_accessed')->nullable();
    });
}

You may not need timestamps() in your migration (chances are you've already got the 'createdat'/'updatedat' columns from a previous migration.

Don't forget your 'last_accessed' column, else you'll get "Column not found: 1054 Unknown column 'last_accessed' in 'field list'".

Remember to run the migration: php artisan migrate

The guard we just created will use the new fields in this table to authenticate against when Auth::guard('customer')->attempt(...) is attempted in the CustomerLoginController.

Create new registration form

Use the defaul registration form template, edit to use usename instead of email for login (if desired).

cp register.blade.php customer-register.blade.php

Be sure to edit the registration form's POST action to the route customer.register.submit otherwise it will attempt to authenticate against the wrong table.

Adapt your model to make it authenticatable

Your additionall user login model (\App\Customer in this case) needs to extend the Auth User class in order to work with the authentication system.

Edit your Customer class:

  • Include User auth: use Illuminate\Foundation\Auth\User as Authenticatable;
  • Change 'extends Model' to 'extends Authenticatable'
  • Add email, username, and password to the protected '$fillable' array property in your customer class*

*This prevents the MassAssignmentException in Model.php error.

<?php
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;

class Customer extends Authenticatable
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
    */
   protected $fillable = [
   'email', 'password', 'username',
   ];
  ... methods specific to this model...
 }

Create a new registration route

The url for customer registrations will be: 'localhost/admin/customer/register' only accessible by the admin user so we need to add a new route for that.

Edit you /var/www/routes/web.php routes file to include a route group for the 'admin' section:

// Admin route group
Route::group(['prefix' => 'admin', 'middleware' => 'auth:web'], 
function() {
     //Create new Customers
     Route::get('customer/register',
    'Auth\[email protected]');
     Route::post('customer/register',      'Auth\[email protected]')->name('customer.register.submit');
     Route::resource('customers', 'CustomerController');
});

Create a new registration controller

Navigate to /var/www/app/Http/Controllers/Auth

Copy the default RegisterController.php

cp RegisterController.php CustomerRegisterController.php

Be sure to

  • Rename the CustomerRegisterController.php class name from RegisterController to CustomerRegisterController.
  • Update the index() method to show the customer registration view (auth.customer-register)
  • Include your Customer model use App\Customer; replacing the default use App\User.

Edit the create() method to create a new customer

Be sure to adapt your CustomerRegisterController to create a new Customer (using your model) and not the default User::create:

protected function create(array $data)
{
  $customer =  Customer::create([
      'email' => $data['email'],
      'password' => bcrypt($data['password']),
      'username' => $data['username'],
  ]);

 return  $customer;
}

Above: CustomerRegisterController.php in /var/www/app/Http/Controllers/Auth

Redirect after registration

Decide where you want to redirect your administrator to after they've created the user. For our use case, we want to allow the administrator to add additional details after creating a login for the customer user type.

So for this example, we remove the default $redirectTo and instead edit the register() method to redirect the administrator to the edit page of the user after their user account is created:

/**
 * (Override) Illuminate\Foundation\Auth::register
 * Handle a registration request for the application.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
public function register(Request $request)
{
    $this->validator($request->all())->validate();

    event(new Registered($user = $this->create($request->all())));
    $request->session()->flash('message', 'Now add the customer details.');
    return redirect()->action([email protected]', [$user]);
}

The above is the CustomerRegisterController.php, within our /var/www/app/Http/Controllers/Auth direcory.

We also add a helpful flash message 'Now add the customer details' which we'll show on the CustomerController edit view.

Created an authenticated area

With all that set-up you're now free to make use of the authentication guard hover you please.

  • Check authentication at the Blade template level
  • Close off entire routes to certain user types (see below)

To lock out a collection of routes to a particular guard / type of login you can create a route group and pass the guard(s) as middleware. This causes Laravel to block access to non authenticated routes.

//Manager Areas
Route::group(['prefix' => 'customer', 'middleware' => 
    'auth:customer'], function()
{
    Route::get('forLogedInCustomerEyesOnly');
    Route::resource('widget', 'WidgetController');
});

The above would go in your web.php, and allow access to these routes only to authenticated customers.