Skip to main content

jwt-auth

The jwt-auth plugin supports the use of JSON Web Token (JWT) as a mechanism for clients to authenticate themselves before accessing upstream resources.

Once enabled, the plugin exposes an endpoint to create JWT credentials by consumers. The process generates a token that client requests should carry to identify themselves to APISIX. The token can be included in the request URL query string, request header, or cookie. APISIX will then verify the token to determine if a request should be allowed or denied to access upstream resources.

When a consumer is successfully authenticated, APISIX adds additional headers, such as X-Consumer-Username, X-Credential-Indentifier, and other consumer custom headers if configured, to the request, before proxying it to the upstream service. The upstream service will be able to differentiate between consumers and implement additional logics as needed. If any of these values is not available, the corresponding header will not be added.

Examples

The examples below demonstrate how you can work with the jwt-auth plugin for different scenarios.

Use JWT for Consumer Authentication

The following example demonstrates how to implement JWT for consumer key authentication.

Create a consumer jack:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jack"
}'

Create jwt-auth credential for the consumer:

curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"secret": "jack-hs256-secret-that-is-very-long"
}
}
}'

Create a route with jwt-auth plugin:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "jwt-route",
"uri": "/headers",
"plugins": {
"jwt-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'

To issue a JWT for jack, you could use JWT.io's JWT decoder or other utilities. If you are using JWT.io's JWT decoder, do the following:

  • Fill in HS256 as the algorithm.
  • Update the secret in the Valid secret section to be jack-hs256-secret-that-is-very-long.
  • Update payload with consumer key jack-key; and add exp or nbf in UNIX timestamp.
note

If you are using API7 Enterprise, the requirement of exp or nbf is not mandatory. You can optionally include these claims and use the claims_to_verify parameter to configure which claim to verify.

Your payload should look similar to the following:

{
"key": "jack-key",
"nbf": 1729132271
}

Copy the generated JWT and save to a variable:

export jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.UEPXy5jpid624T1XpfjM0PLY73LZPjV3Qt8yZ92kVuU

Send a request to the route with the JWT in the Authorization header:

curl -i "http://127.0.0.1:9080/headers" -H "Authorization: ${jwt_token}"

You should receive an HTTP/1.1 200 OK response similar to the following:

{
"headers": {
"Accept": "*/*",
"Authorization": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjY2NDk2NDAsImtleSI6ImphY2sta2V5In0.kdhumNWrZFxjUvYzWLt4lFr546PNsr9TXuf0Az5opoM",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66ea951a-4d740d724bd2a44f174d4daf",
"X-Consumer-Username": "jack",
"X-Credential-Identifier": "cred-jack-jwt-auth",
"X-Forwarded-Host": "127.0.0.1"
}
}

Send a request with an invalid token:

curl -i "http://127.0.0.1:9080/headers" -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjY2NDk2NDAsImtleSI6ImphY2sta2V5In0.kdhumNWrZFxjU_random_random"

You should receive an HTTP/1.1 401 Unauthorized response similar to the following:

{"message":"failed to verify jwt"}

The following example demonstrates how to accept JWT in specified header, query string, and cookie.

Create a consumer jack:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jack"
}'

Create jwt-auth credential for the consumer:

curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"secret": "jack-hs256-secret-that-is-very-long"
}
}
}'

Create a route with jwt-auth plugin, and specify the request parameters carrying the token:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "jwt-route",
"uri": "/get",
"plugins": {
"jwt-auth": {
"header": "jwt-auth-header",
"query": "jwt-query",
"cookie": "jwt-cookie"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'

To issue a JWT for jack, you could use JWT.io's JWT decoder or other utilities. If you are using JWT.io's JWT decoder, do the following:

  • Fill in HS256 as the algorithm.
  • Update the secret in the Valid secret section to be jack-hs256-secret-that-is-very-long.
  • Update payload with consumer key jack-key; and add exp or nbf in UNIX timestamp.
note

If you are using API7 Enterprise, the requirement of exp or nbf is not mandatory. You can optionally include these claims and use the claims_to_verify parameter to configure which claim to verify.

Your payload should look similar to the following:

{
"key": "jack-key",
"nbf": 1729132271
}

Copy the generated JWT and save to a variable:

export jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.UEPXy5jpid624T1XpfjM0PLY73LZPjV3Qt8yZ92kVuU

Verify With JWT in Header

Sending request with JWT in the header:

curl -i "http://127.0.0.1:9080/get" -H "jwt-auth-header: ${jwt_token}"

You should receive an HTTP/1.1 200 OK response similar to the following:

{
"args": {},
"headers": {
"Accept": "*/*",
"Host": "127.0.0.1",
"Jwt-Auth-Header": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.UEPXy5jpid624T1XpfjM0PLY73LZPjV3Qt8yZ92kVuU",
...
},
...
}

Verify With JWT in Query String

Sending request with JWT in the query string:

curl -i "http://127.0.0.1:9080/get?jwt-query=${jwt_token}"

You should receive an HTTP/1.1 200 OK response similar to the following:

{
"args": {
"jwt-query": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.UEPXy5jpid624T1XpfjM0PLY73LZPjV3Qt8yZ92kVuU"
},
"headers": {
"Accept": "*/*",
...
},
"origin": "127.0.0.1, 183.17.233.107",
"url": "http://127.0.0.1/get?jwt-query=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTY5NTEyOTA0NH0.EiktFX7di_tBbspbjmqDKoWAD9JG39Wo_CAQ1LZ9voQ"
}

Sending request with JWT in the cookie:

curl -i "http://127.0.0.1:9080/get" --cookie jwt-cookie=${jwt_token}

You should receive an HTTP/1.1 200 OK response similar to the following:

{
"args": {},
"headers": {
"Accept": "*/*",
"Cookie": "jwt-cookie=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.UEPXy5jpid624T1XpfjM0PLY73LZPjV3Qt8yZ92kVuU",
...
},
...
}

Manage Secrets in Environment Variables

The following example demonstrates how to save jwt-auth consumer key to an environment variable and reference it in configuration.

APISIX supports referencing system and user environment variables configured through the NGINX env directive.

Save the key to an environment variable:

export JACK_JWT_SECRET=jack-hs256-secret-that-is-very-long
tip

If you are running APISIX in Docker, you should set the environment variable using the -e flag when starting the container.

Create a consumer jack:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jack"
}'

Create jwt-auth credential for the consumer and reference the environment variable:

curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"secret": "$env://JACK_JWT_SECRET"
}
}
}'

Create a route with jwt-auth enabled:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "jwt-route",
"uri": "/get",
"plugins": {
"jwt-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'

To issue a JWT for jack, you could use JWT.io's JWT decoder or other utilities. If you are using JWT.io's JWT decoder, do the following:

  • Fill in HS256 as the algorithm.
  • Update the secret in the Valid secret section to be jack-hs256-secret-that-is-very-long.
  • Update payload with consumer key jack-key; and add exp or nbf in UNIX timestamp.
note

If you are using API7 Enterprise, the requirement of exp or nbf is not mandatory. You can optionally include these claims and use the claims_to_verify parameter to configure which claim to verify.

Your payload should look similar to the following:

{
"key": "jack-key",
"nbf": 1729132271
}

Copy the generated JWT and save to a variable:

export jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.UEPXy5jpid624T1XpfjM0PLY73LZPjV3Qt8yZ92kVuU

Sending request with JWT in the header:

curl -i "http://127.0.0.1:9080/get" -H "Authorization: ${jwt_token}"

You should receive an HTTP/1.1 200 OK response.

Sign JWT with RS256 Algorithm

The following example demonstrates how you can use asymmetric algorithms, such as RS256, to sign and validate JWT when implementing JWT for consumer authentication. You will be generating RSA key pairs using openssl and generating JWT using JWT.io to better understand the composition of JWT.

Generate a 2048-bit RSA private key and extract the corresponding public key in PEM format:

openssl genrsa -out jwt-rsa256-private.pem 2048
openssl rsa -in jwt-rsa256-private.pem -pubout -out jwt-rsa256-public.pem

You should see jwt-rsa256-private.pem and jwt-rsa256-public.pem generated in your current working directory.

Visit JWT.io's JWT decoder and do the following:

  • Fill in RS256 as the algorithm.
  • Copy and paste the private key content into the SIGN JWT: PRIVATE KEY section.
  • Update payload with consumer key jack-key; and add exp or nbf in UNIX timestamp.

Your payload should look similar to the following:

{
"key": "jack-key",
"nbf": 1729132271
}
note

If you are using API7 Enterprise, the requirement of exp or nbf is not mandatory. You can optionally include these claims and use the claims_to_verify parameter to configure which claim to verify.

Copy the generated JWT and save to a variable:

export jwt_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.K-I13em84kAcyH1jfIJl7ls_4jlwg1GzEzo5_xrDu-3wt3Xa3irS6naUsWpxX-a-hmcZZxRa9zqunqQjUP4kvn5e3xg2f_KyCR-_ZbwqYEPk3bXeFV1l4iypv6z5L7W1Niharun-dpMU03b1Tz64vhFx6UwxNL5UIZ7bunDAo_BXZ7Xe8rFhNHvIHyBFsDEXIBgx8lNYMq8QJk3iKxZhZZ5Om7lgYjOOKRgew4WkhBAY0v1AkO77nTlvSK0OEeeiwhkROyntggyx-S-U222ykMQ6mBLxkP4Cq5qHwXD8AUcLk5mhEij-3QhboYnt7yhKeZ3wDSpcjDvvL2aasC25ng

Create a consumer jack:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jack"
}'

Create jwt-auth credential for the consumer and configure the RSA keys:

curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"algorithm": "RS256",
"public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoTxe7ZPycrEP0SK4OBA2\n0OUQsDN9gSFSHVvx/t++nZNrFxzZnV6q6/TRsihNXUIgwaOu5icFlIcxPL9Mf9UJ\na5/XCQExp1TxpuSmjkhIFAJ/x5zXrC8SGTztP3SjkhYnQO9PKVXI6ljwgakVCfpl\numuTYqI+ev7e45NdK8gJoJxPp8bPMdf8/nHfLXZuqhO/btrDg1x+j7frDNrEw+6B\nCK2SsuypmYN+LwHfaH4Of7MQFk3LNIxyBz0mdbsKJBzp360rbWnQeauWtDymZxLT\nATRNBVyl3nCNsURRTkc7eyknLaDt2N5xTIoUGHTUFYSdE68QWmukYMVGcEHEEPkp\naQIDAQAB\n-----END PUBLIC KEY-----"
}
}
}'

❶ Configure the consumer key to be jack-key.

❷ Configure the JWT signing algorithm to be RS256.

❸ Configure the RSA public key.

tip

You should add a newline character after the opening line and before the closing line, for example -----BEGIN PUBLIC KEY-----\n......\n-----END PUBLIC KEY-----.

The key content can be directly concatenated.

Create a route with the jwt-auth plugin:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "jwt-route",
"uri": "/headers",
"plugins": {
"jwt-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'

To verify, send a request to the route with the JWT in the Authorization header:

curl -i "http://127.0.0.1:9080/headers" -H "Authorization: ${jwt_token}"

You should receive an HTTP/1.1 200 OK response.

Add Consumer Custom ID to Header

The following example demonstrates how you can attach a consumer custom ID to authenticated request in the Consumer-Custom-Id header, which can be used to implement additional logics as needed.

Create a consumer jack with a custom ID label:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jack",
"labels": {
"custom_id": "495aec6a"
}
}'

Create jwt-auth credential for the consumer:

curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"secret": "jack-hs256-secret-that-is-very-long"
}
}
}'

Create a route with jwt-auth:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "jwt-auth-route",
"uri": "/anything",
"plugins": {
"jwt-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'

To issue a JWT for jack, you could use JWT.io's JWT decoder or other utilities. If you are using JWT.io's JWT decoder, do the following:

  • Fill in HS256 as the algorithm.
  • Update the secret in the Valid secret section to be jack-hs256-secret-that-is-very-long.
  • Update payload with consumer key jack-key; and add exp or nbf in UNIX timestamp.
note

If you are using API7 Enterprise, the requirement of exp or nbf is not mandatory. You can optionally include these claims and use the claims_to_verify parameter to configure which claim to verify.

Your payload should look similar to the following:

{
"key": "jack-key",
"nbf": 1729132271
}

Copy the generated JWT and save to a variable:

export jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.UEPXy5jpid624T1XpfjM0PLY73LZPjV3Qt8yZ92kVuU

To verify, send a request to the route with the JWT in the Authorization header:

curl -i "http://127.0.0.1:9080/headers" -H "Authorization: ${jwt_token}"

You should see an HTTP/1.1 200 OK response similar to the following:

{
"headers": {
"Accept": "*/*",
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.UEPXy5jpid624T1XpfjM0PLY73LZPjV3Qt8yZ92kVuU",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-6873b19d-329331db76e5e7194c942b47",
"X-Consumer-Custom-Id": "495aec6a",
"X-Consumer-Username": "jack",
"X-Credential-Identifier": "cred-jack-jwt-auth",
"X-Forwarded-Host": "127.0.0.1"
}
}

If you would like to attach more consumer custom headers to authenticated requests, see the attach-consumer-label plugin.

Rate Limit with Anonymous Consumer

The following example demonstrates how you can configure different rate limiting policies by regular and anonymous consumers, where the anonymous consumer does not need to authenticate and has less quotas.

Create a regular consumer jack and configure the limit-count plugin to allow for a quota of 3 within a 30-second window:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jack",
"plugins": {
"limit-count": {
"count": 3,
"time_window": 30,
"rejected_code": 429
}
}
}'

Create the jwt-auth credential for the consumer jack:

curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"secret": "jack-hs256-secret-that-is-very-long"
}
}
}'

Create an anonymous user anonymous and configure the limit-count plugin to allow for a quota of 1 within a 30-second window:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "anonymous",
"plugins": {
"limit-count": {
"count": 1,
"time_window": 30,
"rejected_code": 429
}
}
}'

Create a route and configure the jwt-auth plugin to accept anonymous consumer anonymous from bypassing the authentication:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "jwt-auth-route",
"uri": "/anything",
"plugins": {
"jwt-auth": {
"anonymous_consumer": "anonymous"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'

To issue a JWT for jack, you could use JWT.io's JWT decoder or other utilities. If you are using JWT.io's JWT decoder, do the following:

  • Fill in HS256 as the algorithm.
  • Update the secret in the Valid secret section to be jack-hs256-secret-that-is-very-long.
  • Update payload with consumer key jack-key; and add exp or nbf in UNIX timestamp.
note

If you are using API7 Enterprise, the requirement of exp or nbf is not mandatory. You can optionally include these claims and use the claims_to_verify parameter to configure which claim to verify.

Your payload should look similar to the following:

{
"key": "jack-key",
"nbf": 1729132271
}

Copy the generated JWT and save to a variable:

export jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.UEPXy5jpid624T1XpfjM0PLY73LZPjV3Qt8yZ92kVuU

To verify the rate limiting, send five consecutive requests with jack's JWT:

resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/anything" -H "Authorization: ${jwt_token}" -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429

You should see the following response, showing that out of the 5 requests, 3 requests were successful (status code 200) while the others were rejected (status code 429).

200:    3, 429:    2

Send five anonymous requests:

resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/anything" -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429

You should see the following response, showing that only one request was successful:

200:    1, 429:    4
API7.ai Logo

The digital world is connected by APIs,
API7.ai exists to make APIs more efficient, reliable, and secure.

Sign up for API7 newsletter

Product

API7 Gateway

SOC2 Type IIISO 27001HIPAAGDPRRed Herring

Copyright © APISEVEN PTE. LTD 2019 – 2025. Apache, Apache APISIX, APISIX, and associated open source project names are trademarks of the Apache Software Foundation