Custom Auth Driver | Extending Laravel with Managers

Introduction

When developing applications, API developers often face unusual requirements from customers. Often these are requirements to user data security and authentication. And not always ready-made solutions (packages) fit their conditions.

Authentication is the initial entry point into applications and the foundation of web application security. Laravel has a solid foundation for creating secure authentication and a large number of already implemented auth drivers.

However, the requirements for developers creating complex user access structures, there is a need to create their own login driver.

This article is written to help all developers who have encountered the challenge of custom authentication driver in Laravel and describes how to create and integrate them.

Understanding Laravel Authentication

Laravel authentication mechanism is quite complicated, but on the other hand it is very simple if you know how it works.

Laravel developers have already taken care of the mechanism of dealing with drivers and their connection to the framework. Thanks to Taylor Otwell and team.

Laravel authentication is built on two pillars: Guards & Providers.

  • Guards - determine how users will authenticate as users. Through session,cookie, request.
  • Providers - describe how to get data: using Eloquent, request builder, or API.

Laravel uses the Illuminate\Auth\Auth\AuthManager class to manage auth drivers, which combines some of the principles of the Abstract Factory and Mediator design patterns, taking on the responsibility of creating and managing implementations (drivers).

The AuthManager contains a list of available drivers, and also allows you to add your own custom drivers to the DI container as the application loop progresses.

Laravel 10 has 2 default drivers:

  • Session - implemented by the createSessionDriver() method, for standard web applications whose work is limited to PHP sessions. Not suitable for APIs.
  • Token - implemented by the createTokenDriver() method. Designed for API, but is not popular in practice, due to the existence of more convenient drivers. Passport, Sanctum is preferable, but requires more experience than the standard solution.

The number of drivers may vary from Laravel version to version. In Laravel 10, the Sanctum driver is automatically written in composer.json and will be installed with the project. It was created for quick customization and working with the API.

Creating a custom driver is all about its adaptability to any application. By creating a customized solution, developers can fine-tune authentication methods, protocols and algorithms considering all the needs of the application.

Backend curation ensures compliance with specific security standards and allows easy integration with third-party services.

Closure Request Guards

The easiest way to create your own authentication system is to implement it via the Auth::viaRequest() method. It is sufficient to implement only the closure function and return an authorized user.

"Viva La Vida" or viaRequest

public function viaRequest($driver, callable $callback)
{
    return $this->extend($driver, function () use ($callback) {
        $guard = new RequestGuard($callback, $this->app['request'], $this->createUserProvider());

        $this->app->refresh('request', $guard, 'setRequest');

        return $guard;
    });
}

RequestGuard already contains methods for validation and retrieving user data from the database. The class contains useful functions that will help you to implement your own driver without creating everything from scratch.

Here is an example of a simple implementation of your own driver based on viaRequest.

public function boot(): void
{
    Auth::viaRequest('custom-token', function (Request $request) {
        return User::where('token', (string) $request->token)->first();
    });
}

The first parameter we passed was the name of the future driver, the second was the closure function, which will return the user or nothing. RequestGuard contains the user()method where our function falls into.

public function user()
{
    if (! is_null($this->user)) {
        return $this->user;
    }

    // $this->callback - this is our function

    return $this->user = call_user_func(
        $this->callback, $this->request, $this->getProvider()
    );
}

After defining your custom driver, you can configure it as a driver in the Guard configuration of your auth.php configuration file. And then use it as auth:api in any middleware.

'guards' => [
    'api' => [
        'driver' => 'custom-token',
    ],
],

Step-by-step guide to creating a Custom Authentication Driver

Creating a custom driver is not an easy process and a difficult choice for a programmer, as it doesn't always justify the project timeline, but serves as an investment in future projects and team experience.

To get the driver up and running, new directories must be defined, namespaces must be configured, class structures should be well thought out.

Integration involves specifying the driver in the configuration, defining the driver's behavior in the application loop, and ensuring the stability of an already running application with existing authentication components.

1. Create OR Use an existing Laravel Guard

A simple solution would be to use an already pre-built Guard from the Laravel out of the box. For APIs, RequestGuard and TokenGuard are more suitable, they already contain methods to work with Request.

For working with sessions, SessionGuard is suitable, which will get rid of routine operations of getting and saving data to the session.

A more complicated way is to create your custom Guard. First, you need to implement a class that will implement Illuminate\Contracts\Auth\Guard and all the methods of the interface.

It would be nice to define additional functions from GuardHelpers and Macroable to simplify the code.

You can also inherit an existing Guard and modify it.

In this article we will use an already implemented Guard - RequestGuard. For it we will define our callback function as an object through __invoke() (the script tries to execute the object as a function).

class Guard
{
    public function __invoke(Request $request, UserProvider $provider)
    {
        $tokenRepository = app(TokenRepositoryInterface::class);

        $bearerToken = $request->bearerToken();

        if (empty($bearerToken)) {
            return null;
        }

        $accessToken = $tokenRepository->findByToken($bearerToken);

        if (! $this->isValidAccessToken($accessToken)) {
            return null;
        }

        $userId = $accessToken->user_id;

        return $provider->retrieveById($userId);
    }
}

2. Create OR Use an existing UserProvider

Laravel gives you the option to use out-of-the-box solutions that are suitable for working with the DB facade and Eloquent.
The UserProvider is responsible for retrieving Illuminate\Contracts\Auth\Authenticatable implementations from persistent storage. Similarly with Guard, we will use an off-the-shelf implementation.

But you can go the other way and create your own by creating a class with an implementation of the Illuminate\Auth\UserProvider interface as DatabaseUserProvider or EloquentUserProvider.

Auth::createUserProvider($config['provider'] ?? null)

In the configuration file define the provider we want to use.

3. Extending The Framework & Register your Custom Driver

Laravel makes it possible to you the ability to extend cache, session, and authentication functionality through the extend() method of the Auth facade. The best place to do this is in the ServiceProvider in the boot method. The extend fuction takes the name of the new driver. Let's call it trust-token.

Auth::resolved(function (Factory $auth) {
    $auth->extend('trust-token', function (Application $app, string $name, array $config) use ($auth) {

        $guard = new RequestGuard(
            new Guard($auth, $app['config']['trust-token']['expiration'] ?? null),
            $app->get('request'),
            $auth->createUserProvider($config['provider'] ?? null)
        );

        return tap($guard, function ($guard) use ($app) {
            $app->refresh('request', $guard, 'setRequest');
        });
    });
}); 

The code shows that we use RequestGuard and pass it to our callback. Also a configuration file called trust-token.php, which contains additional information like expiration time and the name of the token DB table.

// trust-token.php

return [
    'table' => 'access_tokens',
    'expiration' => 43200,
];

4. Change default auth guard name in Laravel

After registering a driver, you need to switch to the new driver in the config/auth.php configuration file.

'defaults' => [
    'guard' => 'api',
    'passwords' => 'users',
],
'guards' => [
    'api' => [
        'driver' => 'trust-token',
        'provider' => 'users',
    ]
],
'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\Models\User::class,
    ],
],

Usage Examples

In practice, a custom driver should be developed when communicating with proprietary systems that require non-standard authentication methods, such as multi-factor authentication (MFA). Creating a bus for microservices to communicate. Or non-standard solutions such as authenticating under other users without logging out of the system.

Tips, Tricks and Best Practices

  • Developers creating custom authentication drivers should pay special attention to documenting methods and structure. To support and collaborate with the code.
  • Also, applying strong security measures such as encryption and validation. This is a prerequisite to protect the authentication process from potential vulnerabilities and SQL injections.
  • For architecturally simple applications favor already well tested drivers, you will save time and possibly increase the reliability of the application.
  • Take a moment and explore the various Manager classes shipped with Laravel, such as AuthManager or SessionManager. By going deeper into the design you will get a better understanding of how Laravel works "under the hood".
  • By writing your own driver you can always exclude it, from the monolith of the system, into Laravel packages. This way you can isolate the code in a separate repository and develop separately from the project.

Conclusion

In conclusion, although Laravel's standard auth system is fine for most standard authentication cases. However, knowing about custom auth drivers opens up a wide range of possibilities for complex and non-standard solutions.

By allowing developers to customize authentication methods to meet the unique requirements of their applications, such customization not only improves security, but also increases the reliability and adaptability of Laravel-based applications.

Learning the internal kitchen of the framework makes developers more literate in design patterns, building future application architectures, and for revisiting prototypical solutions.

The driver code written in this article is available on GitHub, you can delve into all the details of implementing a custom authorization driver. Perhaps, for some people, this will be the beginning of building their own unique solution.

Similar Articles