There are times when we need a particular section of our app to be accessible only to some particular users. For example, if we are building a forum, we may want to restrict access to /admin routes. In such a scenario, we need separate logins for regular users and users with administrative rights. A custom user authentication system is useful for these scenarios.

Laravel’s out of the box authentication system isn’t very useful for above example. However, because the default auth system is so much flexible, we can easily extend it to implement a multi-auth system.

Setting up the Database

First migrate the default tables that Laravel comes with and then scaffold authentication.

We will add just one column is_admin to the default users table that comes with Laravel. By default, it is set to false. For users with admin privileges, this will be set to true. We will then use this field to filter admin users from basic users.

So create a new migration and update the up() and down() functions.

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->boolean('is_admin')->default(0);
    });
}
 
public function down()
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn('is_admin');
    });
}

Migrate this and we are set to go.

Setting up the Controllers and the Routes

First, let’s create an admin controller.

<?php
 
namespace App\Http\Controllers\Admin;
 
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
 
class AdminController extends Controller
{   
    public function index()
    {
        return "Welcome to admin area";
    }
}

and a corresponding route :

Route::group(['prefix' => 'admin', 'namespace' => 'Admin'], function(){
    Route::get('/', 'AdminController@index');
});

We want this route to be accessible only to users with administrative rights.

Writing a new User Provider

Next, we need to create a new User Provider class, AdminUserProvider , that will retrieve a user by the provided credentials only if the user is an admin.

Since we are using Eloquent, we can simply extend Laravel’s default Illuminate\Auth\EloquentUserProvider class that implements the Illuminate\Contracts\Auth\UserProvider contract.

Now our AdminUserProvider will only retrieve an admin user, so we will just override retrieveByCredentials() method. Also since we are not modifying the basic logic behind retrieving an user, we can simply call the same method on the parent class.

$user = parent::retrieveByCredentials($credentials);

We can now check if an user with the provided details was found and if yes, whether that user is an admin or not.

if($user === null || !$user->is_admin)
{
	return null;
}

If no user was found or if the user is not an admin, we will simply return NULL. Otherwise we will return the user.

The complete class is as follows:

<?php 

namespace App\Libraries;

use Illuminate\Auth\EloquentUserProvider;

class AdminUserProvider extends EloquentUserProvider
{
	public function retrieveByCredentials(array $credentials)
	{
		$user = parent::retrieveByCredentials($credentials);

		if($user === null || !$user->is_admin)
		{
			return null;
		}

		return $user;
	}
}

Now we need to register our custom provider in App\Providers\AuthServiceProvider . In the boot() method, add the following:

Auth::provider('admin-user-provider', function ($app, array $config) {
    return new AdminUserProvider($app['hash'], $config['model']); 
});

admin-user-provider is the driver name that we will use in config\auth.php . Since we are using Illuminate\Auth\EloquentUserProvider , we need the $app and $config to initialize our admin provider. Check out Laravel’s documentation for more information about registering custom providers.

Finally, let’s update the providers array in config\auth.php and add our just created provider.

'providers' => [
    //......
 
    'admin-users' => [
        'driver' => 'admin-user-provider',
        'model' => App\User::class,
    ],
    
    //.......
],

admin-users is our provider’s name that we will use to define a custom guard.

Configuring Guards

Now that our custom user provider is complete, let’s add a new guard that will utilize this provider. In the guards array in config\auth.php , create a new guard.

'guards' => [
    // ....
 
    'admin' => [
        'driver' => 'session',
        'provider' => 'admin-users'
    ],
 
    // ....
],

Since this a web app, we are sticking with the session driver rather than token .

Writing a Middleware

Next we need a custom middleware that only allows admin users to access certain routes. The handle() method will have a $guard parameter that is default to null. We will use this $guard to fetch the actual guard for that request.

$reqGuard = Auth::guard($guard);

Once we have the guard, we can check if the authenticated user is not an admin or if the request is coming from an unauthenticated user. In both case, we want to abort the request and throw an error.

if($reqGuard->guest() || !$reqGuard->user()->is_admin) {
    return abort(401);
}

However if the authenticated user is an admin, we need to pass the request to the next middleware.

return $next($request);

The complete middleware is as follows:

<?php
 
namespace App\Http\Middleware;
 
use Closure;
 
use Illuminate\Support\Facades\Auth;
 
class AuthenticateAdmin
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next, $guard = null)
    {
        $reqGuard = Auth::guard($guard);
 
        if($reqGuard->guest() || !$reqGuard->user()->is_admin) {
            return abort(401);
        }
 
        return $next($request);
    }
}

Finally we register the middleware in App\Http\Kernel.php . In the $routeMiddleware array, add our middleware :

protected $routeMiddleware = [
    // ....
    
    'auth.admin' => \App\Http\Middleware\AuthenticateAdmin::class,
    
    // ....
];

Now we can use this middleware to protect routes.

Let’s stitch everything together!

Our provider, guard and middleware are ready. However if we authenticate our admin users through the standard login form, they still won’t be able to access /admin . This is because they are being authenticated using the web guard. We need to authenticate them using our admin guard that we wrote. For this we need to create a separate login controller that will render a separate login form for our admins.

<?php
 
namespace App\Http\Controllers\Admin;
 
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
 
use Auth;
 
class AdminLoginController extends Controller
{
    use AuthenticatesUsers;
 
    protected $redirectTo = '/admin';
 
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }
 
    public function showLoginForm()
    {
        return view('admin.login');
    }
 
    protected function guard()
    {
        return Auth::guard('admin');
    }
}

We are overriding showLoginForm() and guard() methods from the AuthenticatesUsers trait. Also we set the after login redirection to our /admin route.

For the login form, just copy the the default login template to resources/views/admin/ and update the action field in the form to /admin/login. Finally, let’s update the routes :

Route::group(['prefix' => 'admin', 'namespace' => 'Admin'], function(){
    Route::get('/', 'AdminController@index');
    Route::get('/login', 'AdminLoginController@showLoginForm');
    Route::post('/login', 'AdminLoginController@login');
    Route::get('/logout', 'AdminLoginController@logout');
});

Before we forget, let’s also update our AdminController and add a constructor that uses our AuthenticateAdmin middleware:

public function __construct()
{
    $this->middleware('auth.admin:admin');
}

That’s all ! Now we have multiple authentication mechanism setup for our categorically different users.

Our regular users can login through /login and can’t access the /admin area. However our admin users can login through /admin/login and bingo 🙂


The complete code for the project can be found on Github.