Docs

Implement Basic Authentication

Learn how to set up basic authentication in Apache APISIX, allowing clients to authenticate using a username and password combination securely.

Applies to:Apache APISIX 3.18.0

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

Goal

A route that authenticates two consumers by username and password, forwards their consumer identities upstream, and applies a different request quota to each consumer.

Use HTTPS for client traffic because Basic credentials accompany every request; delegated access and multi-factor authentication require a different authentication method.

Before you begin

  • You have an Apache APISIX installation, cURL, and jq. Get APISIX installs one in Docker or Kubernetes.

Set the variables this guide uses:

export APISIX_ADMIN_URL="http://127.0.0.1:9180"
export GATEWAY_URL="http://127.0.0.1:9080"
read -rsp "Admin API key: " APISIX_ADMIN_KEY && export APISIX_ADMIN_KEY && echo
  • The Admin API accepts your key. Check it with:

    curl -s -o /dev/null -w '%{http_code}\n' "${APISIX_ADMIN_URL}/apisix/admin/routes" \
      -H "X-API-KEY: ${APISIX_ADMIN_KEY}"

    Expected: 200.

  • The data plane is reachable. Check it with:

    curl -sI "${GATEWAY_URL}" | grep -i '^server'

    Expected: a Server: APISIX/3.18.0 header.

  • The consumer names johndoe and janedoe and the route ID basic-auth-route are unused. Check all three with:

    for path in consumers/johndoe consumers/janedoe routes/basic-auth-route; do
      curl -s -o /dev/null -w "${path}: %{http_code}\n" \
        "${APISIX_ADMIN_URL}/apisix/admin/${path}" \
        -H "X-API-KEY: ${APISIX_ADMIN_KEY}"
    done

    Expected: each line ends in 404. A 200 means that identifier already exists; stop and use unique names consistently rather than overwriting it.

Steps

Three steps create the consumer identities and quotas, attach Basic credentials, and expose an authenticated route.

Step 1: Create two consumers with different quotas

Create one consumer allowed one request per 30 seconds and another allowed two.

curl -s "${APISIX_ADMIN_URL}/apisix/admin/consumers" -X PUT \
  -H "X-API-KEY: ${APISIX_ADMIN_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "johndoe",
    "labels": {"custom_id": "john-doe-junior"},
    "plugins": {
      "limit-count": {"count": 1, "time_window": 30, "rejected_code": 429}
    }
  }'

curl -s "${APISIX_ADMIN_URL}/apisix/admin/consumers" -X PUT \
  -H "X-API-KEY: ${APISIX_ADMIN_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "janedoe",
    "labels": {"custom_id": "jane-doe-senior"},
    "plugins": {
      "limit-count": {"count": 2, "time_window": 30, "rejected_code": 429}
    }
  }'
{ "value": { "username": "johndoe", "plugins": { "limit-count": { "count": 1, "time_window": 30, … } } } }
{ "value": { "username": "janedoe", "plugins": { "limit-count": { "count": 2, "time_window": 30, … } } }

What changed: two consumer identities now carry separate local rate limits, but neither can authenticate yet.

Step 2: Create a Basic credential for each consumer

Attach one username and password pair to each consumer.

curl -s "${APISIX_ADMIN_URL}/apisix/admin/consumers/johndoe/credentials" -X PUT \
  -H "X-API-KEY: ${APISIX_ADMIN_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "cred-john-basic-auth",
    "plugins": {"basic-auth": {"username": "johndoe", "password": "john-key"}}
  }'

curl -s "${APISIX_ADMIN_URL}/apisix/admin/consumers/janedoe/credentials" -X PUT \
  -H "X-API-KEY: ${APISIX_ADMIN_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "cred-jane-basic-auth",
    "plugins": {"basic-auth": {"username": "janedoe", "password": "jane-key"}}
  }'
{ "value": { "id": "cred-john-basic-auth", "plugins": { "basic-auth": { "username": "johndoe", … } } } }
{ "value": { "id": "cred-jane-basic-auth", "plugins": { "basic-auth": { "username": "janedoe", … } } } }

What changed: each consumer can now be resolved from its Basic credential.

Step 3: Create an authenticated route

Expose the upstream at /anything and require basic-auth on that route.

curl -s "${APISIX_ADMIN_URL}/apisix/admin/routes/basic-auth-route" -X PUT \
  -H "X-API-KEY: ${APISIX_ADMIN_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "uri": "/anything",
    "plugins": {"basic-auth": {}},
    "upstream": {
      "type": "roundrobin",
      "nodes": {"httpbin.org:80": 1}
    }
  }'
{ "value": { "id": "basic-auth-route", "uri": "/anything", "plugins": { "basic-auth": {} }, … } }

What changed: /anything now rejects requests that do not contain a valid Basic credential.

Validate

Send a request as johndoe and inspect the identity forwarded to the upstream:

curl -s "${GATEWAY_URL}/anything" -u johndoe:john-key | \
  jq '{username: .headers["X-Consumer-Username"], credential: .headers["X-Credential-Identifier"], custom_id: .headers["X-Consumer-Custom-Id"]}'
{
  "username": "johndoe",
  "credential": "cred-john-basic-auth",
  "custom_id": "john-doe-junior"
}

After the current 30-second window expires, send three requests as each consumer and compare their quotas:

resp=$(seq 3 | xargs -I{} curl -s -o /dev/null -w '%{http_code}\n' "${GATEWAY_URL}/anything" -u johndoe:john-key) && \
  echo "johndoe — 200: $(echo "$resp" | grep -c 200), 429: $(echo "$resp" | grep -c 429)"

resp=$(seq 3 | xargs -I{} curl -s -o /dev/null -w '%{http_code}\n' "${GATEWAY_URL}/anything" -u janedoe:jane-key) && \
  echo "janedoe — 200: $(echo "$resp" | grep -c 200), 429: $(echo "$resp" | grep -c 429)"
johndoe — 200: 1, 429: 2
janedoe — 200: 2, 429: 1

An invalid password is rejected:

curl -s -i "${GATEWAY_URL}/anything" -u johndoe:wrong-key | head -1
HTTP/1.1 401 Unauthorized

Roll back / Clean up

Remove the route first, then the credentials and consumers it depended on:

curl -s -X DELETE "${APISIX_ADMIN_URL}/apisix/admin/routes/basic-auth-route" -H "X-API-KEY: ${APISIX_ADMIN_KEY}"
curl -s -X DELETE "${APISIX_ADMIN_URL}/apisix/admin/consumers/johndoe/credentials/cred-john-basic-auth" -H "X-API-KEY: ${APISIX_ADMIN_KEY}"
curl -s -X DELETE "${APISIX_ADMIN_URL}/apisix/admin/consumers/janedoe/credentials/cred-jane-basic-auth" -H "X-API-KEY: ${APISIX_ADMIN_KEY}"
curl -s -X DELETE "${APISIX_ADMIN_URL}/apisix/admin/consumers/johndoe" -H "X-API-KEY: ${APISIX_ADMIN_KEY}"
curl -s -X DELETE "${APISIX_ADMIN_URL}/apisix/admin/consumers/janedoe" -H "X-API-KEY: ${APISIX_ADMIN_KEY}"

The first command takes this guide's /anything route offline. Each successful deletion returns 200; another route can still match the same path.

Troubleshoot

SymptomCauseFix
A request returns 401 with Invalid user authorization.The username or password does not match a basic-auth credential.Compare the -u value with the credential created in step 2, then allow a few seconds for the change to reach the gateway.
The response has no consumer headers.The request did not pass through this route, or another plugin removed the headers before the upstream received them.Confirm the request path is /anything, then inspect the matched route and plugin configuration.
More requests succeed than the configured quota.limit-count uses the local policy by default, so each APISIX instance keeps its own counter.Use a Redis-backed policy (opens in Plugin Hub docs) when multiple gateway instances must share one counter.