HMAC SHA256 Token Authenticator
The HMAC-SHA256 authenticator supports the use of revocable API keys without using OAuth. This provides an alternative to a token that is passed in every request and instead uses a shared secret that is used to sign the request in a secure manner. Like authorization tokens, these are commonly used to provide third-party developers access to your API. These keys typically have a very long expiration time, often years.
These are also suitable for use with mobile applications. In this case, the user would register/sign-in with their email/password. The application would create a new access token for them, with a recognizable name, like "John's iPhone 12", and return it to the mobile application, where it is stored and used in all future requests.
Note
For the purpose of this documentation, and to maintain a level of consistency with the Authorization Tokens, the term "Token" will be used to represent a set of API Keys (key and secretKey).
Usage
In order to use HMAC Keys/Token the Authorization
header will be set to the following in the request:
Authorization: HMAC-SHA256 <key>:<HMAC-HASH-of-request-body>
The code to do this will look something like this:
header("Authorization: HMAC-SHA256 {$key}:" . hash_hmac('sha256', $requestBody, $secretKey));
Using the CodeIgniter CURLRequest class:
<?php
$client = \Config\Services::curlrequest();
$key = 'a6c460151b4cabbe1c1d73e08915ce8e';
$secretKey = '56c85232f0e5b55c05015476cd132c8d';
$requestBody = '{"name":"John","email":"[email protected]"}';
// $hashValue = b22b0ec11ad61cd4488ab1a09c8a0317e896c22adcc5754ea4cfd0f903a0f8c2
$hashValue = hash_hmac('sha256', $requestBody, $secretKey);
$response = $client->setHeader('Authorization', "HMAC-SHA256 {$key}:{$hashValue}")
->setBody($requestBody)
->request('POST', 'https://example.com/api');
HMAC Keys/API Authentication
Using HMAC keys requires that you either use/extend CodeIgniter\Shield\Models\UserModel
or
use the CodeIgniter\Shield\Authentication\Traits\HasHmacTokens
on your own user model. This trait
provides all the custom methods needed to implement HMAC keys in your application. The necessary
database table, auth_identities
, is created in Shield's only migration class, which must be run
before first using any of the features of Shield.
Generating HMAC Access Keys
Access keys/tokens are created through the generateHmacToken()
method on the user. This takes a name to
give to the token as the first argument. The name is used to display it to the user, so they can
differentiate between multiple tokens.
$token = $user->generateHmacToken('Work Laptop');
This creates the keys/tokens using a cryptographically secure random string. The keys operate as shared keys.
The 'key' is stored as plain text in the database, the 'secretKey' is stored encrypted. The method returns an
instance of CodeIgniters\Shield\Authentication\Entities\AccessToken
. The field secret
is the 'key' the field
rawSecretKey
is the shared 'secretKey'. Both are required to when using this authentication method.
The plain text version of these keys should be displayed to the user immediately, so they can copy it for their use. It is recommended that after that only the 'key' field is displayed to a user. If a user loses the 'secretKey', they should be required to generate a new set of keys to use.
$token = $user->generateHmacToken('Work Laptop');
echo 'Key: ' . $token->secret;
echo 'SecretKey: ' . $token->rawSecretKey;
Revoking HMAC Keys
HMAC keys can be revoked through the revokeHmacToken()
method. This takes the key as the only
argument. Revoking simply deletes the record from the database.
$user->revokeHmacToken($key);
You can revoke all HMAC Keys with the revokeAllHmacTokens()
method.
$user->revokeAllHmacTokens();
Retrieving HMAC Keys
The following methods are available to help you retrieve a user's HMAC keys:
// Retrieve a set of HMAC Token/Keys by key
$token = $user->getHmacToken($key);
// Retrieve an HMAC token/keys by its database ID
$token = $user->getHmacTokenById($id);
// Retrieve all HMAC tokens as an array of AccessToken instances.
$tokens = $user->hmacTokens();
HMAC Keys Scopes
Each token (set of keys) can be given one or more scopes they can be used within. These can be thought of as permissions the token grants to the user. Scopes are provided when the token is generated and cannot be modified afterword.
$token = $user->gererateHmacToken('Work Laptop', ['posts.manage', 'forums.manage']);
By default, a user is granted a wildcard scope which provides access to all scopes. This is the same as:
$token = $user->gererateHmacToken('Work Laptop', ['*']);
During authentication, the HMAC Keys the user used is stored on the user. Once authenticated, you
can use the hmacTokenCan()
and hmacTokenCant()
methods on the user to determine if they have access
to the specified scope.
if ($user->hmacTokenCan('posts.manage')) {
// do something....
}
if ($user->hmacTokenCant('forums.manage')) {
// do something....
}
HMAC Secret Key Encryption
The HMAC Secret Key is stored encrypted. Before you start using HMAC, you will need to set/override the encryption key
in $hmacEncryptionKeys
in app/Config/AuthToken.php. This should be set using .env and/or system
environment variables. Instructions on how to do that can be found in the
Setting Your Encryption Key
section of the CodeIgniter 4 documentation.
You will also be able to adjust the default Driver $hmacEncryptionDefaultDriver
and the default Digest
$hmacEncryptionDefaultDigest
, these default to 'OpenSSL'
and 'SHA512'
respectively. These can also be
overridden for an individual key by including them in the keys array.
public $hmacEncryptionKeys = [
'k1' => [
'key' => 'hex2bin:923dfab5ddca0c7784c2c388a848a704f5e048736c1a852c862959da62ade8c7',
],
];
public string $hmacEncryptionCurrentKey = 'k1';
public string $hmacEncryptionDefaultDriver = 'OpenSSL';
public string $hmacEncryptionDefaultDigest = 'SHA512';
When it is time to update your encryption keys you will need to add an additional key to the above
$hmacEncryptionKeys
array. Then adjust the $hmacEncryptionCurrentKey
to point at the new key. After the new
encryption key is in place, run php spark shield:hmac reencrypt
to re-encrypt all existing keys with the new
encryption key. You will need to leave the old key in the array as it will be used read the existing 'Secret Keys'
during re-encryption.
public $hmacEncryptionKeys = [
'k1' => [
'key' => 'hex2bin:923dfab5ddca0c7784c2c388a848a704f5e048736c1a852c862959da62ade8c7',
],
'k2' => [
'key' => 'hex2bin:451df599363b19be1434605fff8556a0bbfc50bede1bb33793dcde4d97fce4b0',
'digest' => 'SHA256',
],
];
public string $hmacEncryptionCurrentKey = 'k2';
public string $hmacEncryptionDefaultDriver = 'OpenSSL';
public string $hmacEncryptionDefaultDigest = 'SHA512';
php spark shield:hmac reencrypt
You can (and should) set these values using environment variable and/or the .env file. To do this you will need to set the values as JSON strings:
authtoken.hmacEncryptionKeys = '{"k1":{"key":"hex2bin:923dfab5ddca0c7784c2c388a848a704f5e048736c1a852c862959da62ade8c7"},"k2":{"key":"hex2bin:451df599363b19be1434605fff8556a0bbfc50bede1bb33793dcde4d97fce4b0"}}'
authtoken.hmacEncryptionCurrentKey = k2
Depending on the set length of the Secret Key and the type of encryption used, it is possible for the encrypted value to
exceed the database column character limit of 255 characters. If this happens, creation of a new HMAC identity will
throw a RuntimeException
.
Configuration
Configure app/Config/AuthToken.php for your needs.
Note
Shield does not expect you use the Access Token Authenticator and HMAC Authenticator at the same time. Therefore, some Config items are common.
HMAC Keys Lifetime
HMAC Keys/Tokens will expire after a specified amount of time has passed since they have been used.
By default, this is set to 1 year. You can change this value by setting the $unusedTokenLifetime
value. This is in seconds so that you can use the
time constants
that CodeIgniter provides.
public $unusedTokenLifetime = YEAR;
Login Attempt Logging
By default, only failed login attempts are recorded in the auth_token_logins
table.
This can be modified by changing the $recordLoginAttempt
value.
public int $recordLoginAttempt = Auth::RECORD_LOGIN_ATTEMPT_FAILURE;
If you don't want any logs, set it to Auth::RECORD_LOGIN_ATTEMPT_NONE
.
If you want to log all login attempts, set it to Auth::RECORD_LOGIN_ATTEMPT_ALL
.
It means you log all requests.
Logging
Login attempts are recorded in the auth_token_logins
table, according to the
configuration above.
When a failed login attempt is logged, the raw token value sent is saved in
the identifier
column.
When a successful login attempt is logged, the token name is saved in the
identifier
column.