How user authentication works in Drupal 8

How user authentication works in Drupal 8

Ever wondered how Drupal 8 authenticates a user? Let's do a deep dive and find out.

In this journey, we will encounter a few new concepts which I'll try and explain briefly here and in detail in separate blog posts. Many of these concepts are borrowed from Symfony and adopted in Drupal 8. The journey of a request begins in a Symfony component called HTTP kernel. The job of HTTP kernel is to handle requests and respond to them in an event driven way.

Now, there might be many other modules and components might want to do stuff when the Drupal site gets a request, or crafts a response etc. The HTTP kernel dispatches "events" to the rest of the system to do exactly this. Any module or component which wants to do stuff, like log to a file if the request is for a specific route or page, can listen to the request event, get notified when a request comes in and do what it wants to do.

This workflow is similar to the hooks system we use. In fact, there was even a discussion to replace hooks with events in core. Hooks vs events is a subject of debate for another day. Back to user authentication!

User authentication works on top of the event system by subscribing to a request event. Here's the service definition of authentication_subscriber in

  class: Drupal\Core\EventSubscriber\AuthenticationSubscriber
  arguments: ['@authentication', '@current_user']
    - { name: event_subscriber }

This event subscriber does the following:

  1. Check if any of the authentication schemes apply to this route.
  2. Authenticate the user using the scheme obtained in the previous step.

The event subscriber by itself doesn't do all these things. It delegates these tasks to another service called Authentication manager. This service is again defined in

  class: Drupal\Core\Authentication\AuthenticationManager
  arguments: ['@authentication_collector']

The Authentication manager service checks if the authentication scheme checks out for the current route. You specify the authentication for a route in your module's .routing.yml file like:

  path: '/foo/bar'
    _auth: [ 'basic_auth', 'cookie' ]
    _controller: '\Drupal\mymodule\MyController::foo'

A service called the Authentication collector fetches all the authentication providers defined in the system. The Authentication Manager service avails the authentication collector service(as shown in the service definition, it's one of the service constructor's arguments) to get information about all the auth providers. This information is twofold.

  1. Whether the given authentication scheme applies to this context. Ex: if we are using token based authentication, is the token to be authenticated present in the request body/headers.
  2. The actual authentication logic.

In addition to basic authentication and cookie-based authentication, Drupal 8 allows developers to define their own customized authentication schemes. You can learn how to write one here.

A collector service is a special type of service tagged as service_collector, which collects other services defined under a specified name. This is used to collect all these similar tagged services, instantiate them and pass these instances to the collector class for further processing. Thus, the authentication collector service collects all authentication provider services, instantiates them and passes them to the Authentication Manager.

  class: Drupal\Core\Authentication\AuthenticationCollector
    - { name: service_collector, tag: authentication_provider, call: addProvider }

If I have defined my own authentication scheme and declared a service and tagged it as authentication_provider, that's the cue for the authentication collector service to pick it up or "collect" it.

  class: Drupal\token_auth\Authentication\Provider\TokenAuth
  arguments: ['@session_configuration', '@database']
    - { name: authentication_provider, provider_id: token_auth, priority: 100 }

The Drupal core defines 2 authentication schemes, a cookie-based scheme in the user module, and another one by name basic authentication, in the basic_auth module. Let's dissect it real quick.

The cookie-based authentication service definition looks like this.

  class: Drupal\user\Authentication\Provider\Cookie
  arguments: ['@session_configuration', '@database']
    - { name: authentication_provider, provider_id: 'cookie', priority: 0, global: TRUE }

The cookie-based authentication scheme returns a session object for the current user(based on the cookie ID in request headers), or a NULL if the user is anonymous. This is the default authentication scheme.

The basic auth scheme works by encoding username and password in the request headers.

  class: Drupal\basic_auth\Authentication\Provider\BasicAuth
  arguments: ['@config.factory', '@user.auth', '@flood', '@entity.manager']
    - { name: authentication_provider, provider_id: 'basic_auth', priority: 100 }

It also implements a flood control policy in addition to basic authentication. What exactly happens when a user submits their username and password in the login form? We'll see that next.