Decoupled Drupal Authentication with OAuth 2.0

Perhaps the most critical component of any decoupled Drupal architecture is a robust authentication mechanism that protects data transmitted between a Drupal site and API consumers like JavaScript applications and native mobile applications. While Drupal core makes available HTTP Basic Authentication and cookie-based authentication, both easy to use, neither of these approaches is sufficiently secure when it comes to best practices.

Fortunately, the Drupal contributed ecosystem contains several highly useful modules that leverage more recent authentication standards like OAuth 2.0 Bearer Token and JSON Web Tokens (JWT), both of which are seeing wide use in the Drupal community among decoupled Drupal practitioners. In the next two installments of Experience Express, we take a breather from voyaging to conferences and inspect authentication best practices in decoupled Drupal, starting with OAuth 2.0.

OAuth 2.0 Bearer Token authentication

At present, OAuth represents one of the most commonly found authentication methods as an open standard for access delegation. Somewhat confusingly, there are two separate modules in Drupal's contributed ecosystem that handle OAuth-based authentication, namely the OAuth module (providing support for the first version of OAuth, no longer recommended) and the Simple OAuth module (providing support for the second and current version, OAuth 2.0).

OAuth works by the server granting access to server resources on behalf of someone who exerts ownership over those resources. OAuth provides access tokens over HTTP to API consumers using an authorization server found in Drupal, which are then used by consumers in subsequent requests to retrieve data or inflict some change on the server.

Authored by Mateu Aguiló Bosch (e0ipso), the Simple OAuth module, by default, provides a password grant, which is an access grant that allows trusted first-party applications to access operations in Drupal. While there are other grant types provided in the OAuth 2.0 Authorization Framework (RFC 6749), and though password grants may allocate excessive control, it is a convenient foundation for authentication in decoupled Drupal.1

Installing Simple OAuth and assigning scopes

Install Simple OAuth using Composer in your project root. This command downloads and installs both the Simple OAuth module and its dependency, the OAuth2 Server package by the PHP League.

$ composer require drupal/simple_oauth:^3

With Simple OAuth installed, our next step is to generate a pair of keys, one public and the other private, to encrypt the tokens generated by Simple OAuth. These keys should be stored outside of the Drupal project root, but save the paths at which they are located for future reference.

In Drupal, scopes in OAuth 2.0 (which define operations that a consumer should be granted access to) are analogous to user roles. Generally, we can couple each consumer, such as a native mobile application, with an individual user role. Now, we can help Drupal know about the consumers that need to be granted OAuth 2.0 tokens.

If you navigate to Configuration » Simple OAuth » Consumers (/admin/config/services/consumer) and click on Add consumer (/admin/config/services/consumer/add), you will see a form that allows us to make your consumer known to Drupal. You'll need to create a name for your consumer and a secret (a password) that the consumer will include in every request.

After creating our consumer in Drupal, we can see its scope and UUID.

Finally, head to the Simple OAuth module configuration page (/admin/config/people/simple_oauth) to add the paths to the public and private key that we generated earlier in the tutorial.2

Creating and verifying access tokens

Now that we've configured the Simple OAuth module, we need to request an access token from the resource located at /oauth/token. The OAuth 2.0 specification stipulates that the OAuth token resource on an authorization server must accept only POST requests whose bodies are formatted in form-data or x-www-form-urlencoded, and any JSON-formatted bodies will be rejected.

You'll need the following parameters to be present in the request body.

grant_type: In most cases, password.

client_id: The UUID of the consumer from the previous section.

client_secret: The client secret provided during the addition of the consumer.

username: The username of the account associated with the consumer.

password: The password for the account associated with the consumer.

When you issue this POST request, you'll receive the following response containing a JSON object with our access token represented, like the one below (tokens have been truncated).

In order to make sure that our access token is operational, we can craft a GET request against the /oauth/debug resource with ?_format=json added to the end. Make sure to add an Authorization header containing "Bearer " (note the space with particular scrutiny), with the access token above appended.

The resulting response from Drupal's authorization server will include a JSON object with the access token repeated as well as the uid of the user whose credentials were supplied in the original request against /oauth/token. Finally, Drupal also adds a list of that user's roles and permissions, reflecting the scopes to which the user has access.3

Issuing requests with OAuth 2.0 authentication

In order to designate OAuth 2.0 as the preferred method to authenticate incoming requests from consumers, we can instruct Drupal to expose certain HTTP methods and REST resources through OAuth 2.0 authentication using either configuration imports or the REST UI module (both covered in a previous installment of Experience Express). We'll use the latter approach here.

Enable both the core REST and REST UI modules.

$ composer require drupal/restui
$ drush en -y rest restui

On the REST UI configuration page (/admin/config/services/rest), you will see a list of resources that core REST exposes in its API. On the Settings for resource Content page (/admin/config/services/rest/resource/entity%3Anode/edit), enable the oauth2 option that appears under the Authenticated providers list. Now, all requests against Drupal nodes will be authenticated through OAuth 2.0.

Once again, we need to retrieve our access token with a POST request with the request body formatted as form-data or x-www-form-urlencoded as before against /oauth/token.

Now, in the request you intend to submit, include an Authorization header with the Bearer prefix, as you can see in the example below. Once Drupal validates the token, it will proceed to serve the request.

Authorization: Bearer eyJ0eXAi0iJKV1Qi[...]

What to do if your access token expires

Once your Drupal site is in production, it isn't a good idea to have a lengthy time to expiration set for issued OAuth 2.0 tokens. If your token does expire, you can easily acquire a new token to keep submitting requests to Drupal. If you remember from several sections ago in this blog post, the response we receive from our request to /oauth/token also includes a refresh token, which lasts longer and are associated with another access token that we can use to substitute the expired token.

Enable the Simple OAuth Extras module contained within the Simple OAuth module in order to access refresh token functionality.

$ drush en -y simple_oauth_extras

Use the refresh token to acquire a new access token by issuing a POST request to /oauth/token containing the following parameters as form-data or x-www-form-urlencoded.

Drupal's authorization server will then respond with a JSON object that includes token_type (Bearer), expires_in (with the new expiry time of the access token), access_token (a new access token encrypted with the private key we previously provided), and refresh_token (a new refresh token for next time).4

If you've allowed your refresh token to expire, you'll have to repeat the entire process to acquire a new access token from the beginning, but you can avert this process by ensuring that your consumer automatically retrieves a new access token just before the refresh token itself expires.

Conclusion

OAuth 2.0 is the most widely used authentication mechanism for decoupled Drupal use cases, and it is far and away more secure than the Basic Authentication approach used in Drupal core by default. As you can see, protecting data from prying eyes is the responsibility of both the consumer and the authorization server, but Simple OAuth makes it much easier thanks to easy-to-use configuration interfaces and strict adherence to the OAuth 2.0 specification.

In the next installment of Experience Express, we examine another authentication method, JSON Web Tokens (JWT), which is newer but has a wide following and is also more secure than OAuth 2.0. While the module underpinning its functionality in the Drupal contributed ecosystem may be less stable, JWT provides another fascinating approach to authentication in decoupled Drupal that make it a compelling choice for practitioners.

Perhaps the most critical component of any decoupled Drupal architecture is a robust authentication mechanism that protects data transmitted between a Drupal site and API consumers like JavaScript applications and native mobile applications. While Drupal core makes available HTTP Basic Authentication and cookie-based authentication, both easy to use, neither of these approaches is sufficiently secure when it comes to best practices.