Skip to main content
AuthenticationMiddleware only knows how to extract Bearer <token> from the Authorization header. For anything else — an API key in X-Api-Key, a signed cookie, mTLS client cert — you have two options:
  1. Keep AuthenticationMiddleware and implement a custom AuthenticatorInterface that is tolerant of your token format (fine if clients still send Authorization: Bearer <key>).
  2. Replace AuthenticationMiddleware with a custom middleware that pulls the credential from wherever it lives, calls your authenticator, and sets the identity on the request.
This guide covers option 2 — because an X-Api-Key header isn’t a bearer token.

1. Define an identity class

You can reuse JwtAuthIdentity if you don’t mind the misleading name. For clarity, define a dedicated one:

2. Implement the authenticator

Keep credential validation isolated from HTTP plumbing. The authenticator takes a raw string and returns an identity or null.
Store hashes, not raw keys. Compare in constant time if you can — a database index lookup by hash does that for you implicitly.

3. Write the middleware

This replaces AuthenticationMiddleware (don’t add both; you’d double up on work).
Notes:
  • Passive, like AuthenticationMiddleware. Invalid or missing keys don’t short-circuit — we let SecurityMiddleware decide based on the route’s RouteAuthorization. This keeps public routes public.
  • Header name variants. ParameterBag::has() is case-sensitive. If your web server normalises headers you may only need one variant, but covering three common casings is cheap insurance.

4. Register it in place of AuthenticationMiddleware

5. Declare the security scheme for OpenAPI

Tell the OpenAPI generator that this API is secured via an apiKey in the X-Api-Key header. The name (ApiKeyAuth below) is what your routes reference.
Swagger UI will prompt the user for a key and send it on matching requests. See OpenAPI Generator / Security Schemes for the full mapping rules.

6. Protect routes

Supporting both JWT and API keys

Run both middlewares. Whichever header the client sent populates the identity; SecurityMiddleware doesn’t care which authenticator was used, only whether the identity satisfies the route policy.
Because both are passive, a request carrying neither credential still falls through as a guest — the route’s RouteAuthorization decides whether that’s allowed. In your ComponentsObject, register both schemes (BearerAuth and ApiKeyAuth). Different routes can require different schemes; the SecuritySchemeObject.name passed to RouteAuthorization picks which one OpenAPI displays.

Rotating keys

Because the authenticator looks up keys by hash, revocation is “delete the row” and rotation is “insert a new row, tell the customer, delete the old row after a grace period”. Nothing is cached at the authenticator level — you rely on your database. If you front the repository with a short-lived cache, remember to invalidate on rotation.