Security
How keys are stored
A key carries 256 random bits. The database stores only an HMAC-SHA256 of the
key. The HMAC secret is derived from
$GLOBALS, so a database
dump alone does not let an attacker check guessed keys. Comparisons are timing safe.
Warning
Rotating the encryption invalidates every API key. Create new keys
and hand them out after a rotation.
The key is printed once by apikey: and cannot be recovered. The
plain-text identifier (t3key_ plus eight characters) identifies a key in
the backend, in apikey: and in logs.
Sending keys
- Send keys over HTTPS only.
- Send them in the
X-orAPI- Key Authorization: Bearerheader. - A key in the query string is always rejected, even when a valid header is
present, because query strings end up in access logs, browser history and
Refererheaders. Treat such a key as leaked and revoke it. The check covers parameter values and names in the query string as sent, including URL-encoded keys and parameters that a later one of the same name overwrites.
Caching
The frontend middleware disables the TYPO3 page cache for requests with a
valid key, so key-dependent content never reaches anonymous visitors or other
keys (see Caching). A reverse proxy or CDN in front of TYPO3
does not know about keys: let requests with an X- or
Authorization header bypass its cache, otherwise it may answer them with a
page it cached for anonymous visitors.
Apache and the Authorization header
Apache with PHP-FPM or FastCGI does not pass the Authorization header to
PHP by default, and the .htaccess that TYPO3 ships does not forward it
either. Bearer tokens then never reach TYPO3. Either use the X-
header, or forward the header in your virtual host or .htaccess:
# Apache 2.4.13 and newer
CGIPassAuth On
# older versions
SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1
nginx with PHP-FPM passes the header without configuration.
Revoking keys
apikey: hides the key record, and so does hiding it in the
backend. Deleting the record works too. A key also stops working when it
expires or when its bound user is deleted, disabled or outside its start and
end time.
Logging
Rejected keys are logged as warnings by the logger of
\Maik, with the
reason, the identifier, the client address and the URL without query string.
The key itself is never logged. Configure a writer for that class to collect
the entries, e.g. in config/system/additional.php:
use Psr\Log\LogLevel;
use TYPO3\CMS\Core\Log\Writer\FileWriter;
$GLOBALS['TYPO3_CONF_VARS']['LOG']['MaikSchneider']['ApiKey']['writerConfiguration'] = [
LogLevel::WARNING => [
FileWriter::class => ['logFileInfix' => 'api_key'],
],
];
Clients get the same response for every reason, so they can't tell an unknown key from a revoked one.
Reporting a vulnerability
Please do not report security issues in public GitHub issues. Report them to the TYPO3 Security Team at security@typo3.org.