As an aspiring web artisan developing on Laravel, the responsibility for knowing how authentication works in my chosen framework is on me. Previously I had thrown up my hands and trusted in Laravel’s occult magic to authenticate my users for me. When I couldn’t implement a custom authentication method, I knew that relying on the dark arts of others would no longer suffice. In order to master the web application craft, I’d need to embrace the authentication process and understand it head-on.
This column is an expanded and edited version of the notes I took for myself on that journey. I’m sure I’m rehashing to some degree what’s already been said before — Laravel’s own documentation on authentication is, per usual, comprehensive and illuminating. However, the documentation is focused on implementation, not how things work under the hood, and I couldn’t find anything else that described the authentication process in a way that I could wrap my head around.
Authentication in Laravel
There are two major areas in the Laravel request lifecycle where authentication comes into play:
- When a user attempts to log in
- When a user attempts to access authenticated content
The two application flows (login and authenticated content) share much of the same underlying functionality. Both are powered by Laravel’s mix of guards, drivers and providers, which we’ll cover in detail in a moment. The differences are significant enough, however, to where they warrant separate columns, or else run the risk of this column being significantly too long.
Because I’m researching this topic in order to support an API, my research has focused on the authenticated content flow, as my login process won’t interact with the Laravel default.
Accordingly, I’ll be focusing on the per-request authentication process in this column; Part 2 will cover the login process and consider the similarities between them.
The goal of this two-part series is to understand the authentication (auth) process well enough to write custom drivers and providers with full confidence that we know what’s going on under the hood. I’ll cover some of the parts I had trouble with, in hopes that a future someone can avoid the same stumbling blocks I encountered.
Authentication Within the Laravel Request Lifecycle
In order to understand how authentication works on a per-request basis, let’s discern its role within the lifecycle of a Laravel request.
After your application has been bootstrapped and before a given request hits your controller logic, every Laravel request goes through a middleware pipeline. Though you’ve probably encountered middleware in your applications and perhaps even written a few custom middleware yourself, their role within your application has always been a bit opaque, at least to me.
Probably the best analogy I’ve found in explaining the role of middleware is the experience of going through an airport on the way to your flight. After arriving at the airport and before your plane takes off for parts unknown, there are several checkpoints you’ll go through on the way to your airline seat. Some of these checkpoints have the power to reject you (if TSA doesn’t like the size of your toothpaste container, for instance) and some simply streamline the process of getting to your seat (like boarding by ticket zone).
Substitute checkpoints for middleware, and takeoff for the launch of your application, and you have a good idea of how the Laravel request lifecycle works. Your request goes through various middleware checkpoints, some of which transform the request and some of which can reject the request entirely.
Authentication is one of the latter group — it’s middleware that can, and does, reject requests that don’t match the criterion that you’ve defined for your application. Essentially, think of authentication as the TSA of your web application, but if TSA worked instantly and with near 100% accuracy.
By default, this middleware is defined within your
app\Http\Kernel.php file, which points to the Laravel middleware at
From here, as you might know already, you can assign the authentication middleware to any route in your application. Here’s a simple example:
The call to
middleware tells our application to send requests to this route through the
Authenticate middleware. The colon between
api tells Laravel to pass everything after the colon into the middleware as an argument. In this case, we're telling our application that we want to use the
api guard to protect this specific endpoint.
Before we get any further, though, there’s something we need to talk about.
Guards, Providers, and Drivers, Oh My!
By far one of my biggest hurdles in wrapping my head around Laravel authentication was parsing the difference between the different tools at your disposal.
There are three distinct types involved:
Part of the reason I decided to write this series was to wrap my head around these concepts. Several days of research later, I still can’t say that I fully understand the distinctions as clearly as I’d like — but I believe I understand it well enough to explain here.
In part, my confusion is fueled by the practice within Laravel of sometimes using guards and drivers interchangeably and sometimes treating them as distinct units. If I can find a way to resolve this discrepancy cleanly without introducing other problems, I’ll submit a pull request to do so. For now, I hope that a clear explanation of my understanding will suffice.
Guards are the top-level authentication abstraction available within Laravel. If you’re interacting with authentication within the context of your application, you’ll refer to guards, not providers or drivers.
- Guards consist of a driver and a provider.
Providers specify how users are defined within your application. The two batteries-included drivers within Laravel are
Eloquent uses Eloquent to fetch a User model, where
database pulls your users directly from the database, in case your user tables don't adhere to the default
Drivers are the “logic” behind your auth process. Once your provider retrieves a suitable user, your driver checks that user to see whether they’re allowed to authenticate on your website.
- The default driver for APIs is
token, which checks to see whether the user has an
api_tokenfield that matches the
api_tokenprovided by the request.
I tried to think of an analogy that could help to define the relationship between these three concepts, and mostly fell flat.
The closest thing I could come up with is Mary Shelley’s Frankenstein. The reason this metaphor works better than the rest is because, just as guards and drivers can be easily confused, most people believe that the name Frankenstein refers to the monster, not the doctor who created it.
Under this analogy, guards are Dr. Frankenstein, thanklessly combining providers (the monster’s body parts) and drivers (the monster’s life force, and a healthy dose of electricity) to create something truly unique — only to be confused for eternity with his own creation.
Like capitalism, my analogy is the worst one possible, except for all the rest of them. I’ll happily accept pull requests for a better analogy, but until then, Frankenstein helped me, so Frankenstein stays.
A Tour Through the Code: Authentication Under the Hood
Ham-fisted analogies out of the way, let’s get into some code.
Authenticate middleware, also available here:
handle method is the primary function of all middleware, so let's start there. Like all middleware, our
handle method accepts the request and the next middleware in the stack. If our
authenticate method doesn't throw an exception, the request moves on to the next checkpoint.
You’ll notice that this middleware also accepts a third parameter — an array of guards. These guards are specified when you define your routes, like we did with our
api route earlier. Anything after the colon will be converted into an array and passed to the
handle method to be used in our auth process.
As it happens, the
handle method delegates much of the responsibility for authentication to the
authenticate method, which in turn uses the
auth property defined in the
If no guards are passed to
authenticate, our application falls back to the default guard. Otherwise, we'll load each provided guard and use it on our request to see if authentication was successful. If one of the guards passes, our application sets that as the guard that should be used and allows the request to continue. Otherwise an
AuthenticationException is thrown, terminating the request. Next time, bring a travel size.
As it turns out, the
Auth\Factory middleware is bound to the
AuthManager class within
Illuminate\Auth, which as it turns out is the epicenter for much of Laravel's authentication functionality.
Inside the Auth Factory
AuthManager class is fairly large, and as such can be intimidating if you attempt to understand it all at once. Instead, let's start at the methods we've already seen in use and work inwards from there.
Assuming we’ve defined a guard in our routes, the entry point from the
Authenticate middleware to the
AuthManager class is the
Again, a fairly straightforward method (I love how many of those there are in Laravel).
First we find out which guard we need to load. If we’re provided the name of a guard we use that, otherwise the default is pulled in from your application’s
Next, we see whether this guard has been loaded into memory already. If it has, we use it; if not, we load it using the
resolve method and the name of the guard we want.
The first thing that the
resolve method does is load the necessary configuration from your
auth.php file. To do this, it goes through each defined guard and returns the name of the driver and provider for the guard you've specified. If it can't find the guard, an exception is thrown.
Next, it checks to see whether you’re using a custom driver, and if so loads your guard using the custom driver you’ve created. If you’re writing your own driver, your own code will take over from here, but let’s inspect the Laravel defaults for now.
The next step is to find out which first-party driver should be used to create your guard. To find out, it defines a method name by using the driver name you provided in
auth.php and checks if the
AuthManager contains such a method. If it does, that method is called and returned as the correct guard. Otherwise, an exception is thrown, and your request's journey ends here.
You might notice the first sign of interchangeability between guards and drivers. By the method’s own documentation, the returned value is a resolved instance of a
Guard. However, the methods it's calling and returning under the hood are all variations of
createSessionDriver, etc. Could these functions be called
createSessionGuard without breaking Laravel in some forbidden way? Let's find out.
We create a new provider based on what’s defined in our
auth.php, then use that provider to create a
TokenGuard. After refreshing our request to use the new guard we've just created, we return the guard.
It would definitely appear as if this function could be renamed to
createTokenGuard with a minimum of confusion. As the
TokenGuard contains within itself all the driver logic that it will use to authenticate our user, the two concepts appear in this case to be interchangeable.
At this point, the guard is returned to our
Authenticatable middleware, and the
check method is called on each guard returned. In order to understand what the
check method does, we've got to dig deeper into the
Guard class to understand how it interacts with the provider and driver.
Inside the TokenGuard
Before getting into implementation details, sometimes I like to look at interfaces for a basic understanding of what a given class is supposed to do.
We see a lot of the functionality that we’d expect out of a top-level authentication object.
There are methods to extract basic information about a user, methods to check a user’s status within your application, and methods to validate and load users into your guard.
We see lots of methods that we’d expect our drivers and providers to implement under the hood, supporting our thesis that the guard is a top-level wrapper around these functionalities.
As it turns out, most of these methods are implemented in the
GuardHelpers trait, which is shared between the various first-party guards.
check method, as implemented by the
Very simple — it calls the
user method and determines whether there's something there.
user method does depends on the guard you're using, although it should always return a user if it can find one. Let's check out the
user method within the
After checking for and potentially returning a preloaded user, the method defines
null. Unless this variable is populated by subsequent functions, it will remain
null and the
check method will fail, causing the guard to fail in turn.
user method attempts to pull a token from the request using the
getTokenForRequest method. If the method returns a token (ie. if
$token is not empty), a user is then fetched from the provider using the token from the request. Ideally, we want to find a user whose
api_token is identical to the token we find in the request.
If this description is a bit abstract, let’s run through an example. If I want to authenticate over a given API, I could request a token from the API service. When I get the token, I can include it in my API calls using the
api_token field, either as an input in a
POST request or as a query string in a
Then, during the authentication process, the
TokenGuard will fetch the
api_token from the request while calling the
user method we're looking at now. If that token matches the token retrieved by the user provider, we're good to use the API — or, at least, to continue onto the next middleware check. If not, an exception is thrown, and we're left to navigate a cold, bleak, API-less world.
We’ll look more into the specific drivers and providers that comprise the Laravel authentication experience in Part 2 of this series, in which we’ll also cover the login process. With a better understanding of the way in which drivers, providers and guards interact within the auth process, the next column will include more code and less shark-jumping metaphors.