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.0header. -
The consumer names
johndoeandjanedoeand the route IDbasic-auth-routeare 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}" doneExpected: each line ends in
404. A200means 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: 1An invalid password is rejected:
curl -s -i "${GATEWAY_URL}/anything" -u johndoe:wrong-key | head -1HTTP/1.1 401 UnauthorizedRoll 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
| Symptom | Cause | Fix |
|---|---|---|
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. |
Related
- Consumers — how APISIX associates plugins and credentials with an API consumer.
- Credentials — how credentials belong to consumers.
basic-authplugin reference (opens in Plugin Hub docs) — every authentication field and response behavior.limit-countplugin reference (opens in Plugin Hub docs) — quota keys, counter policies, and rejected responses.- Configure HTTPS between clients and APISIX — protect credentials while they cross the network.