Docs
API7 GatewaySecurity and complianceAPI SecurityConfigure Key Authentication

Configure Key Authentication

Learn how to secure your APIs by requiring clients to provide a unique API key in the request header or query string.

Applies to:API7 Gateway 3.10.x

Roles:Platform engineerTime:10 minutesImpact:No restart, no traffic change

Goal

A route that rejects unkeyed requests with 401 and forwards keyed requests to its upstream, with the caller identified as a named consumer.

The key-auth (opens in Plugin Hub docs) plugin reads the key from a request header or, at lower priority, from a query string parameter, and API7 Gateway resolves it against its consumer credentials before the request reaches the upstream. Anonymous consumers, credential forwarding and the full field list are in the plugin reference (opens in Plugin Hub docs).

Before you begin

Set the variables this guide uses before running the prerequisite checks:

export API7_ADMIN_URL="https://localhost:7443"
export API7_GATEWAY_GROUP_ID="default"   # only the group the quickstart creates; otherwise copy the gateway group's UUID from Gateway Groups in the Dashboard
export GATEWAY_URL="http://127.0.0.1:9080"
read -rp "Gateway group name for ADC (if used): " API7_GATEWAY_GROUP_NAME && export API7_GATEWAY_GROUP_NAME
read -rsp "Dashboard token: " API7_ADMIN_KEY && export API7_ADMIN_KEY && echo
  • An API7 Gateway control plane is reachable and your token is valid. Check it with:

    curl -ks -o /dev/null -w '%{http_code}\n' "${API7_ADMIN_URL}/api/gateway_groups" \
      -H "X-API-KEY: ${API7_ADMIN_KEY}"

    Expected: 200.

  • A gateway group exists and at least one gateway instance is running in it. Check it with:

    curl -ks "${API7_ADMIN_URL}/api/gateway_groups/${API7_GATEWAY_GROUP_ID}/instances" \
      -H "X-API-KEY: ${API7_ADMIN_KEY}" | grep -o '"status": *"Healthy"' | wc -l

    Expected: 1 or more — the number of instances reporting Healthy.

  • For the ADC path, ADC is installed:

    adc --version

    Expected: a version string. ADC resolves --gateway-group by display name, so API7_GATEWAY_GROUP_NAME is a different value from the UUID in API7_GATEWAY_GROUP_ID; enter the name of the same group.

Steps

Three steps: a service that knows where traffic goes, a route that closes it, and a consumer that can open it.

Step 1: Create a service with an upstream

Give the traffic a destination.

curl -ks "${API7_ADMIN_URL}/apisix/admin/services/key-auth-httpbin-service?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -X PUT \
  -H "X-API-KEY: ${API7_ADMIN_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "key-auth-httpbin-service",
    "upstream": {
      "type": "roundrobin",
      "scheme": "http",
      "nodes": [
        {
          "host": "httpbin.org",
          "port": 80,
          "weight": 100
        }
      ]
    }
  }'
{ "value": { "id": "key-auth-httpbin-service", "name": "key-auth-httpbin-service", … } }

What changed: the gateway knows where to send this traffic, and nothing reaches it yet.

Step 2: Create a route and enable key-auth

Publish the service at a path, and close it in the same request.

curl -ks "${API7_ADMIN_URL}/apisix/admin/routes/key-auth-route?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -X PUT \
  -H "X-API-KEY: ${API7_ADMIN_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "key-auth-route",
    "paths": ["/anything/key-auth"],
    "methods": ["GET"],
    "service_id": "key-auth-httpbin-service",
    "plugins": {
      "key-auth": {
        "header": "apikey"
      }
    }
  }'
{ "value": { "id": "key-auth-route", "name": "key-auth-route", … } }

What changed: /anything/key-auth is live and returns 401 to every request, because no credential exists yet.

Step 3: Create a consumer and its credential

Create the identity, then the key that resolves to it.

# 1. Create the consumer
curl -ks "${API7_ADMIN_URL}/apisix/admin/consumers/key-auth-consumer?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -X PUT \
  -H "X-API-KEY: ${API7_ADMIN_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "key-auth-consumer"
  }'

# 2. Create the key-auth credential
curl -ks "${API7_ADMIN_URL}/apisix/admin/consumers/key-auth-consumer/credentials/key-auth-consumer-cred?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -X PUT \
  -H "X-API-KEY: ${API7_ADMIN_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "key-auth-consumer-cred",
    "plugins": {
      "key-auth": {
        "key": "secret-api-key"
      }
    }
  }'
{ "value": { "username": "key-auth-consumer", … } }
{ "value": { "name": "key-auth-consumer-cred", … } }

What changed: one key now opens the route, and the gateway can name who used it.

Validate

Send one request without the key and one with it:

curl -is "${GATEWAY_URL}/anything/key-auth" | head -1
curl -is "${GATEWAY_URL}/anything/key-auth" -H "apikey: secret-api-key" | head -1
HTTP/1.1 401 Unauthorized
HTTP/1.1 200 OK

The keyed response carries the caller's identity, which is what proves the consumer resolved rather than that the key merely matched:

curl -s "${GATEWAY_URL}/anything/key-auth" -H "apikey: secret-api-key"
{
  "headers": {
    "Apikey": "secret-api-key",
    "X-Consumer-Username": "key-auth-consumer",
    "X-Credential-Identifier": "key-auth-consumer-cred"
  }
}

Roll back / Clean up

Remove everything this guide created, in reverse dependency order. Deleting the credential closes the route to every client; deleting the route takes the path offline.

curl -ks -X DELETE "${API7_ADMIN_URL}/apisix/admin/consumers/key-auth-consumer/credentials/key-auth-consumer-cred?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -H "X-API-KEY: ${API7_ADMIN_KEY}"
curl -ks -X DELETE "${API7_ADMIN_URL}/apisix/admin/consumers/key-auth-consumer?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -H "X-API-KEY: ${API7_ADMIN_KEY}"
curl -ks -X DELETE "${API7_ADMIN_URL}/apisix/admin/routes/key-auth-route?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -H "X-API-KEY: ${API7_ADMIN_KEY}"
curl -ks -X DELETE "${API7_ADMIN_URL}/apisix/admin/services/key-auth-httpbin-service?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -H "X-API-KEY: ${API7_ADMIN_KEY}"

Between the first command and the third, requests to /anything/key-auth return 401; after the third they return 404.

Troubleshoot

SymptomCauseFix
The keyed request still returns 401. The body says which case it is: {"message":"Invalid API key in request"} means the gateway found a key and could not resolve it to a consumer; {"message":"Missing API key found in request"} means it found no key at all.Invalid: the credential has not reached the gateway instance yet, or the key you send is not the one the credential holds. Missing: the key is not where the plugin looks for it.Invalid: wait a few seconds and retry, then re-run step 3 and compare the key. Missing: compare the header you send with plugins.key-auth.header on the route.
The request returns 404.The route has not reached the gateway instance yet, or it belongs to a different gateway group.Wait a few seconds and retry, then check that API7_GATEWAY_GROUP_ID is the group whose instance reports Healthy.
The key works in the header but not in the query string.key-auth reads the query string at lower priority and from its own parameter, which defaults to apikey.Set query (opens in Plugin Hub docs) on the plugin, or send the key in the header.