Securely store API credentials with the Drupal Key module
This article is a slightly-edited excerpt from DrupalEasy's 90-hour Professional Module Development course.
When writing a custom Drupal module that requires authentication with a 3rd party service, many new developers struggle with finding an easy and secure method to store the API keys. Storing API keys in the database is usually not a good idea - nor is hard-coding them or saving them in the files directory 😱
A better solution would be to store the credentials outside of the docroot - which is a more secure location. When using this method, if possible, it is recommended to not commit the credentials to the project's Git repository.
Note: there are other methods for storing API credentials outside of the project root as well in a secure manner.
Drupal's Key module is a flexible solution that not only achieves this goal, but also includes a service class that we can easily integrate with our code to access the credentials. The Key module documentation provides an example of how to utilize the service.
Let's look at the example of adding the Key module's service class to a base implementation of a plugin type in the module my_module in order to store GitHub personal access tokens.
Our first step is to make the Key module a dependency of my_module implementing the plugin base class. We'll do this by adding the following to the require section of the module's composer.json:
"drupal/key": "^1.0"
Once added, use Composer to update the module in order to install the Key module:
> composer update drupaleasy/my_module --with-dependencies
While the previous addition will add it to the code base, we also want the module enabled whenever my_module is enabled. To accomplish this, add the following to the dependencies section of the my_module.info.yml file:
- key:key
For now though, as we don't want to uninstall then reinstall my_module just to enable the Key module, let's just use Drush:
> drush en -y key
The Key module provides several options for storing credentials, including in the Drupal database and in an external file, among other solutions. As indicated above, we'll store credentials in a file in the main project directory (above the document root).
Let's first set up the file by creating a new keys directory in the project root. From there, create a new github.key file with the following content in JSON format:
{"username":"YOUR-GITHUB-USERNAME","personal_access_token":"YOUR-GITHUB-PERSONAL-ACCESS-TOKEN"}
Save and close the file, then navigate to the Key module's configuration page at /admin/config/system/keys and click to add a new key with the following values:
- Key name: Github
- Key type: Authentication (multivalue)
- Key provider: File
- File location: ../keys/github.key
Click to save, and the setup of the Key module and our Github credentials is complete!
Next, we'll inject then utilize the service that the Key module provides to access our credentials from my_module's base plugin class.
First, add a new property for the Key service:
/**
* The Key repository service.
*
* @var \Drupal\key\KeyRepositoryInterface
*/
protected KeyRepositoryInterface $keyRepository;
While we can name the variable $keyRepository whatever we'd like, the namespace of the service's interface must be deduced by taking a quick look at the codebase of the Key module. Services are always stored in the /src/ directory; combine that with the fact that the sample code shows the unique string identifier of the service to be key.repository, it is a safe bet that web/modules/contrib/key/src/KeyRepository.php is the service class we're looking for.
Another, and perhaps even easier method of identifying the namespace of the service class is by the Key module's key.services.yml file. A quick peek and it is easy to find key.repository as well as the Drupal\key\KeyRepository namespace.
Often, a service will have an associated interface (so that other modules can implement their own, related credential storage systems,) and it is often preferable to use that, hence the \Drupal\key\KeyRepositoryInterface type hint used above.
As usual, we'll also add a use statement for the interface:
use Drupal\key\KeyRepositoryInterface;
Next, we'll add the key.repository service to the create() method (bold text is new):
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): DrupaleasyRepositoriesPluginBase {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('key.repository'),
);
}
Finally, we'll add it to the __construct() as well (bold text is new):
public function __construct(array $configuration, $plugin_id, $plugin_definition, MessengerInterface $messenger, KeyRepositoryInterface $key_repository) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->keyRepository = $key_repository;
}
Now that we have access to the Key module's key.repository service in our base plugin, let's put it to use in our Github plugin.
As the Key module's documentation demonstrates, we can ask the key.repository service to fetch us our Github key (as we defined in the Key module's UI), and then provide us the values of the key as an associative array (as we defined in the github.key file):
// Get the array of key values from the file.
$github_key = $this->keyRepository->getKey('github')->getKeyValues();
// Use the key values.
$this->client->authenticate($github_key['username'], $github_key['personal_access_token'], AuthMethod::CLIENT_ID);
DrupalEasy's Professional Module Development is a 90-hour, best-practice focused course designed to provide developers with the skills and confidence necessary to be a full-time Drupal module developer. A Lite version of the course (60 hours) is also available. Learn more.
The pixel art image used in this blog post was generated by the DALL-E project of OpenAI.