# Configure API key authentication Type of document: How-to guide Product: F5 NGINX Gateway Fabric > Send API keys in a header or a query parameter to protect your apps with the AuthenticationFilter CRD in F5 NGINX Gateway Fabric. --- This page describes how to set up API key authentication in F5 NGINX Gateway Fabric using the AuthenticationFilter custom resource definition (CRD). API key authentication looks for a key in each request. Clients send the key in a request header or a URL query parameter. NGINX Gateway Fabric loads those keys from a Kubernetes Secret into the NGINX data plane, which checks each request and rejects unknown keys before they reach your apps. Use API key authentication for machine-to-machine traffic, such as a backend service or partner integration calling your API. For user sign-in, use [OpenID Connect authentication](/ngf/traffic-security/oidc-authentication.md) or [basic authentication](/ngf/traffic-security/basic-authentication.md) instead. By following these instructions, you create two sample app endpoints: one that requires an API key and one that doesn't. You then send requests with a valid key, an invalid key, and no key, and compare the results. **Note:** Keys in a URL are less private than keys in a header. They can appear in browser history, in `Referer` headers sent to other sites, and in logs at every hop between the client and your app. Prefer a header unless your clients can't set one. If you do use a query parameter, serve traffic over [HTTPS](/ngf/traffic-management/https-termination.md). ## Before you begin - [Install](/ngf/install/) NGINX Gateway Fabric. ## Set up the example resources Create the sample apps, the Gateway, and the API keys. Each step gives you YAML to apply with `kubectl`. ### Deploy the sample applications Create the coffee and tea applications: ```yaml kubectl apply -f - < ``` Replace `` with the port number you use to access your Gateway. ## Create the Secret and the filter Apply the Secret and the filter together: ```yaml kubectl apply -f - < ``` ## Protect a route Apply the HTTPRoute that attaches the filter to `/coffee`. The `/tea` route has no filter, so you can compare the two: ```yaml kubectl apply -f - < ``` ## Test your configuration **Note:** Your clients need to resolve `cafe.example.com` to the IP address of the NGINX Service. This guide does that with the curl `--resolve` option. Store the keys in shell variables. `GOOD_KEY` holds a key from the Secret and `BAD_KEY` holds a value that isn't in it: ```shell GOOD_KEY=key-abc123 BAD_KEY=not-a-real-key ``` Send the key in a header: ```shell curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/coffee -H "X-API-Key: $GOOD_KEY" ``` ```text Server address: 192.0.2.7:8080 Server name: coffee-654ddf664b-nhhvr Date: 19/Oct/2026:15:20:15 +0000 URI: /coffee Request ID: 13a925b2514b62c45ea4a79800248d5c ``` The key matches an entry in the Secret, so the request reaches the app. Send the key in a query parameter: ```shell curl --resolve cafe.example.com:$GW_PORT:$GW_IP "http://cafe.example.com:$GW_PORT/coffee?api_key=$GOOD_KEY" ``` ```text Server address: 192.0.2.7:8080 Server name: coffee-654ddf664b-nhhvr Date: 19/Oct/2026:15:21:30 +0000 URI: /coffee?api_key=key-abc123 Request ID: 7b2e4a1c9f0d3e5a8c6b4d2f1a0e9c8b ``` Both sources are enabled, so the query parameter works the same way as the header. Send the request without a key: ```shell curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/coffee ``` ```text 401 Authorization Required

401 Authorization Required


nginx
``` Neither source has a key, so NGINX Gateway Fabric responds with `401`. Send a key that isn't in the Secret: ```shell curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/coffee -H "X-API-Key: $BAD_KEY" ``` ```text 403 Forbidden

403 Forbidden


nginx
``` A `403` means NGINX Gateway Fabric checked the key and rejected it. A `401` means the request had no key to check. Send a request to the route with no filter: ```shell curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/tea ``` ```text Server address: 10.244.0.10:8080 Server name: tea-75bc9f4b6d-ms2n8 Date: 19/Oct/2026:15:36:26 +0000 URI: /tea Request ID: c7eb0509303de1c160cb7e7d2ac1d99f ``` The `/tea` route has no AuthenticationFilter, so NGINX Gateway Fabric forwards the request as it is. ## The filter at a glance The `apiKey` fields in the filter you created work as follows: | Field | Description | | ----- | ----------- | | `type` | Set to `APIKey` to use API key authentication. A filter with `type: APIKey` can't set `spec.jwt`, `spec.oidc`, or `spec.basic`. | | `apiKey.secretRef` | Name of the Secret that holds the keys. The Secret must be in the same namespace as the filter. Required. | | `apiKey.sources` | Where clients can send the key. Set one or two sources. Required. | | `sources[].type` | `Header` to read a request header, or `Query` to read a URL query parameter. | | `sources[].name` | Header name or query parameter name. Use letters, numbers, `-`, and `_`, up to 253 characters. | ## How the check works **Keys that get accepted** NGINX Gateway Fabric reads the keys from the `auth` entry of the Secret and loads them into the NGINX data plane. The data plane matches keys exactly, so an uppercase letter or a trailing space makes a key invalid. The client name isn't used to authorize a request. The name tells you which key belongs to whom, so you can revoke one client without touching the others. **Sources, where the key comes from** A request passes when any source carries a valid key, so clients can use whichever one suits them. Header names are case-insensitive, so a source named `x-api-key` matches an `X-API-Key` header. A query parameter name can't contain a hyphen. NGINX can't read query arguments whose names include `-`, so `api-key` is rejected while `api_key` is fine. ## Additional configuration ### Use your own keys The example Secret holds base64-encoded keys, which is easy to copy but hard to edit. Create a file with your own keys, then build the Secret from it. Create the `auth` file: ```shell cat < auth client-mobile: client-web: EOF ``` Replace each `` with a key of your own. Generate keys with a random generator, for example `openssl rand -hex 24`. Apply the Secret. The `--from-file=auth` flag names the data entry `auth`: ```shell kubectl create secret generic api-keys --from-file=auth --dry-run=client -o yaml | kubectl apply -f - ``` Check what NGINX Gateway Fabric reads by decoding the Secret: ```shell kubectl get secret api-keys -o jsonpath='{.data.auth}' | base64 --decode ``` ### Change or revoke a key Keys come from the Secret, so you can change them without touching the filter or your routes. Change the `auth` file and run the same `kubectl apply` command. NGINX Gateway Fabric reads the changed Secret and updates the keys NGINX accepts. To revoke a client, remove that client's entry from the `auth` file and re-apply the Secret again. Requests using that key will then return `403`. ### Accept keys in a header only Drop the query source so clients must use a header: ```yaml spec: type: APIKey apiKey: secretRef: name: api-keys sources: - type: Header name: X-API-Key ``` ### Keep keys out of access logs Routes with API key authentication log the request path without the query string. Keys sent as query parameters don't end up in the NGINX access log. Other systems can still record the full URL. Your app and any proxy between the client and the Gateway can log the key, so headers stay the safer option. ### Allow browser requests Browsers send an unauthenticated `OPTIONS` request before a cross-origin call. When you set [CORS](/ngf/traffic-security/cors.md) on the same route, NGINX Gateway Fabric answers that request before it looks for a key. Only the real call needs a key. ### Give each client group its own keys One Secret means one key list for every route that uses the filter. To give a group of clients its own keys, create a Secret and a filter for that group. Then reference the filter on the routes that group uses. ```shell kubectl create secret generic partner-keys --from-literal=auth="partner-a:" --dry-run=client -o yaml | kubectl apply -f - ``` ## Troubleshooting - Check that the filter is accepted with `kubectl describe authenticationfilters.gateway.nginx.org api-key-auth`. An `Invalid` condition tells you what to fix. - If the filter is rejected with a message about the Secret, check two things. The Secret must be in the same namespace as the filter, and its data entry must be named `auth`. - If the filter is rejected with a message about `client-name:api-key` entries, every line failed. Check that each line has a name, a colon, and a key, and that the keys use only supported characters. - If the filter is rejected with `query parameter names must not contain hyphens`, rename the source. Replace `-` with `_` in the name. - If valid requests get `403`, compare the key the client sends with the decoded Secret, character by character. The check is case-sensitive and rejects extra spaces. - If requests without a key get `200`, check that the route rule references the filter. The `extensionRef` needs `group: gateway.nginx.org`, `kind: AuthenticationFilter`, and the filter name. - If requests stop working after you change a key, check that `kubectl get secret api-keys -o jsonpath='{.data.auth}' | base64 --decode` shows the keys you expect. NGINX Gateway Fabric ignores duplicate keys. ## Further reading - [AuthenticationFilter API reference](/ngf/reference/api.md#gateway.nginx.org/v1alpha1.AuthenticationFilter) - [Configure basic authentication](/ngf/traffic-security/basic-authentication.md) - [Configure external authentication](/ngf/traffic-security/external-authentication.md) - [Configure JSON Web Token (JWT) authentication](/ngf/traffic-security/jwt-authentication.md) - [Configure Cross-Origin Request Sharing (CORS)](/ngf/traffic-security/cors.md) - [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/)