---
title: "Security"
manual: "API_KEY"
version: "1.0"
permalink: "https://docs.typo3.org/permalink/maikschneider/api-key:security@1.0"
source: "Security/Index.rst"
rendered: "2026-10-27T08:22:54+00:00"
---

# Security {#security}

## How keys are stored {#security-storage}

A key carries 256 random bits. The database stores only an HMAC-SHA256 of the
key. The HMAC secret is derived from
`$GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']`, so a database
dump alone does not let an attacker check guessed keys. Comparisons are timing safe.

> [!WARNING]
> Rotating the `encryptionKey` invalidates every API key. Create new keys
> and hand them out after a rotation.

The key is printed once by `apikey:create` and cannot be recovered. The
plain-text *identifier* (`t3key_` plus eight characters) identifies a key in
the backend, in `apikey:list` and in logs.

## Sending keys {#security-transport}

-   Send keys over HTTPS only.
-   Send them in the `X-API-Key` or `Authorization: Bearer` header.
-   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
    `Referer` headers. 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 {#security-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](https://docs.typo3.org/permalink/maikschneider/api-key:developer-caching@1.0)). A reverse proxy or CDN in front of TYPO3
does not know about keys: let requests with an `X-API-Key` or
`Authorization` header bypass its cache, otherwise it may answer them with a
page it cached for anonymous visitors.

### Apache and the Authorization header {#security-apache}

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-API-Key`
header, or forward the header in your virtual host or `.htaccess`:

```apache
# 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 {#security-revocation}

`apikey:revoke` 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 {#security-logging}

Rejected keys are logged as warnings by the logger of
`\MaikSchneider\ApiKey\Authentication\ApiKeyAuthenticator`, 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`](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Configuration/Typo3ConfVars/Index.html#file-project-config-system-additional-php):

```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 {#security-reporting}

Please do not report security issues in public GitHub issues. Report them to
the TYPO3 Security Team at [security@typo3.org](mailto:security@typo3.org).
