Compared to big frameworks or systems, I personally prefer small, tiny, swappable packages — things like Idiorm & Paris or Phinx. For one, they’re easy to understand, with fewer layers of abstraction. For another, they keep coupling low across a project, so you can swap or adjust a piece directly when you need to (say, when a package stops being maintained).
The downside, of course, is that you have to wire everything together by hand. Standalone packages may not sit well with your core system, or they may clash with each other (when they pull in different versions of a shared dependency, for instance). And a single project ends up with many different design mindsets running through its code, each of which you have to learn one by one.
Even so, the design thinking behind today’s major frameworks is well worth learning from. Once a system grows past a certain size, it inevitably has to lean on abstraction and interfaces to stay organized and gain flexibility. That’s an unavoidable path.
Auth, Guard, User: the flow in practice
Back to the topic. Next we’ll walk through the full journey of authentication in detail. I’ve added two pieces to the diagram — Lumen and the AuthServiceProvider — to make the connections clearer.
Lumen → ServiceProvider → AuthManager

To enable authentication in a Lumen system, we first need to register the AuthServiceProvider into the Lumen Application — that is, into $app. This is also where the flow begins:
$app->register(\App\Providers\AuthServiceProvider::class);
How does the AuthServiceProvider bind these services into the system? Looking at the source, the boot method shows this:
$this->app['auth']->viaRequest('api', function ($request) {
...
});
First, this asks for $this->app['auth']. app['auth'] is an object that doesn’t exist to begin with — accessing it triggers the creation of an AuthManager. But why Illuminate\Auth\AuthManager specifically? That’s set by the core Illuminate\Auth\AuthServiceProvider. Here’s how it plays out:
- The code accesses
app['auth']. appchecks whetherauthhas already been built; if not, it instantiates it.- Because the Lumen Application — Laravel\Lumen\Application — binds
authas a special keyword, instantiatingauthtriggers theregisterAuthBindingsfunction. - The
registerAuthBindingsfunction registers the core Illuminate\Auth\AuthServiceProvider service. - In its
registermethod, Illuminate\Auth\AuthServiceProvider uses the singleton patternapp->singletonto instantiate the Illuminate\Auth\AuthManager class and assign it toapp['auth'].
So apart from calling app['auth'] and doing a little light work on top, App\Providers\AuthServiceProvider doesn’t really load any service content of its own — most of the handling is done by the core auth bindings.
The next bit of code, app['auth']->viaRequest, is tied to the Guard, so we’ll cover it together in the next section.
AuthManager → XXX (Guard) → GuardDriver → Guard

So how does the AuthManager interact with the Guard? A Lumen system has three built-in guard classes:
- RequestGuard: request-based, an extensible authentication mode. You must pass in a callback when the object is created; that callback receives two arguments,
$requestanduserProvider, and is expected to return a usable user object (not just an authentication result). - TokenGuard: also request-based, an authentication mode that checks the incoming content — for example, whether the header carries a specific token or password.
- SessionGuard: an authentication mode based on session (and cookie) data — the usual way a typical PHP program records and identifies a logged-in user.
Once app['auth'] — the AuthManager — has been instantiated, we can use it to get any of the guard objects above (or your own custom guard):
- Call
app['auth']->guard('XXX'). If a guard object for the name XXX already exists, it’s returned; if not, it prepares to build one. Note that when no name is passed, it automatically usesdefaults.guardfrom config/auth.php. - Before building the guard object, it first checks whether
guardsin config/auth.php has settings for the XXX guard. If not, it throws an error. - Taking the XXX guard’s settings together, it checks in order whether the guard has:
- A custom callback builder: a builder defined with a callback via
app['auth']->extend()in your code. You also have to set this definition’s name in the config file. - A driver method inside the AuthManager:
- If you’ve extended the AuthManager yourself, you can define a createXXXDriver method to drive the XXX guard.
- The built-in guards’ default driver methods work the same way — for example, SessionGuard uses createSessionDriver, and TokenGuard uses createTokenDriver.
- A custom callback builder: a builder defined with a callback via
- Call the builder / driver method to instantiate the target guard, and store the object under the key XXX in the AuthManager’s own
guards array. - Return the guard object.
From the flow above, you can see that the simplest way to define your own guard is to use app['auth']->extend() to define the function that builds your guard, and to write the settings into config/auth.php.
Coming back to app['auth']->viaRequest: viaRequest is really an extend() built on top of RequestGuard. In plain terms, it builds a RequestGuard object for you and gives you a way to pass in a user callback. Let’s look at these two bits of code:
The AuthServiceProvider’s boot method:
$this->app['auth']->viaRequest('api', function ($request) {
if ($request->input('api_token')) {
return User::where('api_token', $request->input('api_token'))->first();
}
});
The AuthManager’s 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;
});
}
When the AuthServiceProvider boots, viaRequest() uses extend() to define a builder / driver method named api, and passes in a callback — one that takes the api_token from the request and looks the user up in the database — as a constructor argument for the RequestGuard. The next time app['auth']->guard('api') is called, the guard is instantiated and returns the user object according to that callback.
Wrapping up
The AuthManager is probably the most complicated object in the whole authentication architecture. On top of its own job, it uses __call to implement dynamic calls that forward to the guard — meaning you can write app['auth']->guardFunc() in place of app['auth']->guard()->guardFunc(). It really does make the architecture hard to trace back…
This series keeps growing the more I write. In the next post we’ll keep going deeper into the relationship between the AuthManager and the UserProvider. Thanks for reading, everyone — see you next time.


