Laravel Authentication: Under The Hood

Mario Vega
Jul 24, 2018 · 11 min read
Image for post
Image for post

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

  • 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.

Image for post
Image for post

Authentication Within the Laravel Request Lifecycle

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.

Found footage of hackers attempting to hack a Laravel application.

By default, this middleware is defined within your app\Http\Kernel.php file, which points to the Laravel middleware at Illuminate\Auth\Middleware\Authenticate.

From here, as you might know already, you can assign the authentication middleware to any route in your application. Here’s a simple example:

Image for post
Image for post

The call to middleware tells our application to send requests to this route through the Authenticate middleware. The colon between auth and 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!

There are three distinct types involved:

  • Guards
  • Providers
  • Drivers

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 and database. 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 User class.

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_token field that matches the api_token provided 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.

Image for post
Image for post
IT’S AUTHENTICATED!!!!

A Tour Through the Code: Authentication Under the Hood

Here’s the Authenticate middleware, also available here:

Image for post
Image for post

The 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 Auth\Factory contract.

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

Assuming we’ve defined a guard in our routes, the entry point from the Authenticate middleware to the AuthManager class is the guard method:

Image for post
Image for post

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 config/auth.php file.

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.

Image for post
Image for post

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 createTokenDriver, createSessionDriver, etc. Could these functions be called createTokenGuard and createSessionGuard without breaking Laravel in some forbidden way? Let's find out.

Image for post
Image for post

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

Image for post
Image for post

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.

Here’s the check method, as implemented by the GuardHelpers trait:

Image for post
Image for post

Very simple — it calls the user method and determines whether there's something there.

What this 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 TokenGuard class:

Image for post
Image for post

After checking for and potentially returning a preloaded user, the method defines $user as 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.

Next, the 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 GET request.

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.

Conclusion

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store