Unboxing Laravel Authentication

The dreaded ... Auth

Posted by lagbox on March 12, 2017 laravel authentication

Laravel Auth

Laravel has a nice out of the box authentication system that has been around for awhile and is always being worked on. Though it is more extendable and configurable now doesn't entirely mean it is crazy complicated.

Overview

In most of your applications you will be dealing with the authentication system. You probably have a working registration and login system as well as having routes protected from unauthenticated users. You can get this all up and working quickly and without thinking much about how this is all implemented.

When you are dealing with the authentication system there are a few pieces in play. We have a User Provider and a Guard that we are configuring as a guard for the AuthManager which is helping manage all these pieces for us and provides us with the necessary guards.

Guess what? I am the manager, b

The AuthManager (Illuminate\Auth\AuthManager) is the main starting location for dealing with the authentication system. The AuthManager has the ability to create drivers (Guard classes) and providers (User Providers). When we ask it for a guard it can create the driver and provider needed based on the configuration and return us a Guard ready to use. The manager also keeps track of the current User Resolver to be shared with the other services.

If you plan on extending the authentication system you will be doing that via this manager class.

Find me that user

A big piece of this system is being able to query some type of storage for information about a User. The User Provider is the interface to which the Guard will use to retrieve a user from persistent storage and validate any credentials. Laravel comes with two User Providers out of the box: (in Illuminate\Auth)

  • EloquentUserProvider uses Eloquent Models when interacting with the persistence layer. This provider returns an instance of the Eloquent Model defined in the configuration as the User.
  • DatabaseUserProvider uses the Query Builder directly to interact with the persistence layer and will return a GenericUser as the representation of the authenticated User.

The EloquentUserProvider requires a model variable to be defined in its configuration and the DatabaseUserProvider needs a table variable to be defined.

Who's watching the door?

The Guards (driver in the guards config) implement how users are authenticated based on the request. They do the work of checking the conditions and making the decisions based on what they are provided. They deal with the current request and use a User Provider to interact with a persistence layer. Laravel also comes with two Guards out of the box: (in Illuminate\Auth)

  • SessionGuard uses the session to check if the current user is already authenticated or checks via a recaller cookie, 'remember me', and will use the session to save that state of the user after a successful login.
  • TokenGuard uses a token sent with each request to "login" the user by token to the system for the current request.

The auth Config

The configuration we have for authentication has a little more going on but its those parts above (Guard, User Provider) being configured individually and assembled together.

From config/auth.php:

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'api' => [
            'driver' => 'token',
            'provider' => 'users',
        ],
    ],
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],
    ],				

Broken down:

  • guards - The name you use when referencing these combinations of driver and provider when using the AuthManager
    • driver - Guard classes - session, api, ...
    • provider (references providers below)
  • providers - The name we assign to the User Provider and configuration combination
    • driver - User Provider - eloquent, database, ...
    • additional configuration variables for the driver 'model' => ... or 'table' => ...

Defaults

Using the defaults setup you will be dealing with the App\User model, via the EloquentUserProvider, and the SessionGuard. This is the basic normal setup you would have been used to for years. The session will be used to identify the user as authenticated.

From config/auth.php:

    'defaults' => [
        'guard' => 'web', // ['driver' => 'session', 'provider' => 'users']
        'passwords' => 'users',
    ],
 

Interacting

Auth and auth()

What is going on with our facade and our helper function that Laravel has provided us?

  • If we check our trusty Facade Class Reference from the documentation we find out that the Auth Facade's root class is Illuminate\Auth\AuthManager that is the binding auth.
  • Jumping into Illuminate\Auth\AuthServiceProvider we see auth being bound as a singleton and that closure returns a Illuminate\Auth\AuthManager instance.
  • If we check /Illuminate/Foundation/helpers.php@auth we see an instance of Illuminate\Contracts\Auth\Factory is being resolved from the container.
  • And some aliasing ... Illuminate\Foundation\Application@registerCoreContainerAliases
    • 'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class]

Behind the scenes

You are interacting with the AuthManager but mostly a particular guard when making calls to the manager. Do to the lovely nature of our dynamic __call method any methods that aren't accessible on AuthManager are being called upon the default guard. This is where the 'defaults' from the the config come into play.

  • Auth::attempt(...) -> Auth::guard()->attempt(...)
  • Auth::check() -> Auth::guard()->check()

The 'default' guard can and will be adjusted at run time as needed. This yields the advantage of not needing to know exactly which guard was actually used to do the authentication.

The User Resolver

The User Resolver is the method which is used to resolve the current authenticated user. This gets adjusted through the lifecycle of the application depending upon what guard ends up authenticating. Examples that use this resolver:

$request->user();
Auth::user();
 	 

Other services will use this resolver to get the current user. From the Illuminate\Auth\AuthManager source:

    /**
     * The user resolver shared by various services.
     *
     * Determines the default user for Gate, Request, and the Authenticatable contract.
     *
     * @var \Closure
     */
    protected $userResolver;

    /**
     * Create a new Auth manager instance.
     *
     * @param  \Illuminate\Foundation\Application  $app
     * @return void
     */
    public function __construct($app)
    {
        $this->app = $app;

        $this->userResolver = function ($guard = null) {
            return $this->guard($guard)->user();
        };
    }
			

How the resolver is ... resolved?

When it comes to Request@user the AuthServiceProvider sets a rebinding on request to call setUserResolver on the request. A closure is passed to setUserResolver which calls the function returned from AuthManager@userResolver to get the current resolver registered with the manager. The closure passed to setUserResolver receives a $guard variable.

Illuminate\Contracts\Auth\Authenticatable is bound to call the function returned from AuthManager@userResolver.

Dynamic resolvers

You may have wondered why when using the apigroup of middleware that $request->user() returns the correct user from the api guard and doesn't use the default web guard.

When we use the auth(Authenticate) middleware depending upon the guard (if one is passed) this will set the current userResolver based on the guard by calling AuthManager@shouldUse($guard). This tells the manager to now use this guard as the default, which means it can resolve the current user without specifying which guard to use when calling it.

Wrap it up, b

The AuthManager is the main part of the authentication system. It pulls all the pieces together into something you can use (guard [driver and provider]) through out your application. It has changed over the years to allow you to extend and change what pieces it uses internally. From the outside it looks very much the same.

You really don't need to know all of this to use the authentication system, but it is nice to know what is going on.