fromSeptember 2014
Article:

Protecting Your Drupal 8 Resources

Authentication
0

 Penrhyn Castle Drupal 8 incorporates a Modular Authentication System which, given a request, attempts to identify a Drupal user by inspecting the HTTP request headers.

Authentication comes in handy when we want to restrict access to a resource in Drupal. It can be applied to any route, although the method to implement it may differ. It is most commonly used to identify requests when we are exposing data through an API from our Drupal site.

Authentication and Authorization

Imagine you are going through airport security. The security agent asks to see your ID – a passport or driver’s license, say. The act of showing your ID is what we call Authentication. In Drupal – as in almost all websites – your authentication credentials are your username and password.

Next, the security agent checks your boarding pass to verify that you are in the right place and have clearance to get on a plane. That’s called Authorization. In Drupal your role (and therefore the permissions assigned to that role) are your Authorization credentials.

To summarize: authentication means who are you?; authorization means may you proceed?.

Enjoy your flight!

Authentication in Drupal 8

In Drupal 8, Authorization is handled by the Access System and won't be covered in this article; there is an internal system to handle Authentication, so let's start with the following statement:

Thanks to the Modular Authentication System, different Authentication Providers may extract a $user out of a given $request object.

There are a few keywords in that statement. Let's dissect them briefly:

  • The Module Authentication System manages methods to authenticate a given request. It loops them, passing the request and expecting NULL or a Drupal user.
  • An Authentication Provider is a method of identifying a user by inspecting the HTTP request headers. For example, Basic Auth checks that there is a username and a password at the Authorization header, while Cookie checks if there is a cookie ID, and searches for it at the sessions table.
  • The $user object is no longer a POPO (Plain Old PHP Object), but an instance of the UserSession class which represents a Drupal user.
  • The $request is an object oriented representation of an HTTP request. It is powered by the Symfony HttpFoundation component and allows, among other things, access to the request headers.

Let's use an example to illustrate the above. Below, we can see an HTTP client that is making a request to a Drupal 8 REST resource. We are requesting the node with nid 1, and identifying ourselves through Basic Auth. We also give permission to the Authenticated user role so a user with username test and password test has permission to access this node.

Basic Auth sample request and response

As we can see above we get a successful response (code 200 OK) and on the right-hand side there is the HAL+JSON representation of the node that we requested.

Available Authentication Providers

Drupal 8 ships with two Authentication Providers in core: Cookie and Basic Auth. Cookie-based authentication is the default: it looks for a cookie ID in the request headers and returns an existing user ID if a match is found. If no match is found, the anonymous user object is returned.

Basic Auth is a simple authentication method where a username and its password are encoded and appended to the request headers to identify a user. You may have seen this method in action when accessing a protected website with your browser and seeing a popup window requesting username and password. Here is an example:

Basic Auth protocol is supported by browsers, which prompt a form to authenticate.

In contrib we currently have the OAuth module. With OAuth you can create a pair of keys for a Drupal user and hand them to someone so he can identify his requests. Here is an example where we will give REST access to nodes through this method. We start by installing the OAuth module and creating a pair of OAuth keys for a Drupal user:

In OAuth module we can create credentials per Drupal user.

Then, we configure the node REST resource so it requires OAuth authentication and supports JSON format:

Here we are requiring OAuth to access nodes in our REST API.

Finally, we can make the following request using an OAuth client like Guzzle:

<?php
/**
 * @file oauthRequest.php
 * Performs an OAuth request to retrieve a node.
 */
require 'vendor/autoload.php';
use Guzzle\Http\Client;
$client = new Client('http://d8.local');
$client->addSubscriber(new Guzzle\Plugin\Oauth\OauthPlugin(array(
  'consumer_key'  => '3Pm5qMsEPLZavxEDLaMhFWKBEdV525MM',
  'consumer_secret' => 'mRQy4FEKbHetqHeFUHm3yBC7YqwEKpgs',
)));
$request = $client->get('node/1', array(
    'Accept' => 'application/json',
  ),
  array('debug' => TRUE));
try {
  $response = $request->send()->json();
  print_r($response);
}
catch (\Exception $e) {
  print_r($e->getMessage());
}
?>

How to Require Authentication

There are several ways to require authentication to a given route. Let's say that we want to require Basic Auth to the contact form. (Our clients will love us for doing so!) The path is /contact, and by inspecting core/modules/contact/contact.menu_links.yml, we see that the identifier of the route is contact.site_page. Now, here is how we can alter this route to require an authenticated user:

<?php
/**
 * @file
 * Contains \Drupal\friendly_support\Routing\FriendlySupportRouteSubscriber.
 */
 
namespace Drupal\friendly_support\Routing;
 
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
 
/**
 * Listens to the dynamic route events.
 */
class FriendlySupportRouteSubscriber extends RouteSubscriberBase {
  /**
   * Alters existing routes for a specific collection.
   *
   * @param \Symfony\Component\Routing\RouteCollection $collection
   *   The route collection for adding routes.
   */
  public function alterRoutes(RouteCollection $collection) {
    // Load the route, set authentication and add it again.
    $route = $collection->get('contact.site_page');
    $route->setOption('_auth', array('basic_auth'));
    $route->setRequirement('_user_is_logged_in', 'TRUE');
    $collection->add('contact.site_page', $route);
  }
}

When we open http://drupal8.local/contact we will see the following:

After altering the Contact route, it requires authentication credentials.

You can find the full code for this example at http://wdog.it/4/2/dfs

Adding authentication to custom routes is pretty straightforward since it can be set at the route definition. In Drupal 8, routes are defined in YAML files. Authentication is just another property of the route as an array of supported authentication methods.

Let's suppose we have created a custom page to share secret data with a selected number of people. The route definition would be as it follows:

# File: mymodule.routing.yml
mymodule.secret_data:
  path: '/secret_data'
  options:
    _auth: [ 'basic_auth', 'oauth' ]
  requirements:
    _user_is_logged_in: 'TRUE'
  defaults:
    _content: '\Drupal\mymodule\MyModuleController::secretData'

The above settings require an authenticated user through either Basic Auth or OAuth authentication methods. REST module supports authentication so you can set it while defining a REST resource. In Drupal 8, REST resources are defined in YAML files per entity type, where each of them lists the available HTTP methods and their properties. You can find some examples at the REST documentation at Drupal.org.

Note that there is a contributed module called REST UI which allows you to configure REST resources through the user interface.

Conclusion

You are now fully armed to protect your Drupal resources, no matter what they are. You also know which authentication methods are available both in core and in contrib.

If you ever need to implement your own authentication method, remember to contribute back to the community! You can take OAuth module as a template and start from there. The Drupal Core AuthenticationProviderInterface is pretty well documented too.

There is still work to do in the Authentication area of Drupal 8: Search for Authentication in the core issue queue to learn more.

Image: "Penrhyn Castle" by Bert Kaufmann is licensed under CC BY-SA 2.0