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.

Examples

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

Expose JWT Signing Endpoint

This is a preliminary step to expose the JWT signing endpoint in APISIX. If you are using symmetric algorithms such as HS256 (default) or HS512 where APISIX will be both the JWT issuer and validator, this step is mandatory. If you are using asymmetric algorithms such as RS256 or ES256, this step is optional as the issuer and validator can be different parties.

The jwt-auth plugin creates an internal endpoint at /apisix/plugin/jwt/sign to sign JWT. Expose the endpoint with the public-api plugin:

curl "http://127.0.0.1:9180/apisix/admin/routes/jwt-auth-api" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"uri": "/apisix/plugin/jwt/sign",
"plugins": {
"public-api": {}
}
}'

Note that at this stage, the jwt-auth plugin has not been enabled anywhere, so if you send a request to the signing endpoint, you are expected to get a 404 Not Found.

Use JWT for Consumer Authentication

The following example demonstrates how to implement JWT for consumer authentication and see the token expiration in effect.

Before proceeding, please ensure that the JWT signing endpoint has been exposed.

Create a consumer with jwt-auth plugin and set the expiration time of the token to 30 seconds:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jack",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"exp": 30
}
}
}'

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
}
}
}'

Send a request to the signing endpoint to get JWT:

jwt_token=$(curl -s "http://127.0.0.1:9080/apisix/plugin/jwt/sign?key=jack-key") && echo $jwt_token

You should see a token similar to the following:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTY5NTAyOTUxMX0.jdZ2iGtXqEMW6QIECTz8CWjBtLt3UMFS9R2j6dVuPJM

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": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTY5NTAyOTUxMX0.jdZ2iGtXqEMW6QIECTz8CWjBtLt3UMFS9R2j6dVuPJM",
"Host": "127.0.0.1",
"User-Agent": "curl/7.74.0",
"X-Amzn-Trace-Id": "Root=1-65081904-5a1f18143c29bd2d7d6be0d7",
"X-Forwarded-Host": "127.0.0.1"
}
}

In 30 seconds, the token should expire. Send a request with the same token to verify:

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

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.

Before proceeding, please ensure that the JWT signing endpoint has been exposed.

Create a consumer with jwt-auth plugin:

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

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
}
}
}'

Send a request to the signing endpoint to get JWT:

jwt_token=$(curl -s "http://127.0.0.1:9080/apisix/plugin/jwt/sign?key=jack-key") && echo $jwt_token

You should see a token similar to the following:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTY5NTEyOTA0NH0.EiktFX7di_tBbspbjmqDKoWAD9JG39Wo_CAQ1LZ9voQ

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": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTY5NTEyOTA0NH0.EiktFX7di_tBbspbjmqDKoWAD9JG39Wo_CAQ1LZ9voQ",
...
},
...
}

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": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTY5NTEyOTA0NH0.EiktFX7di_tBbspbjmqDKoWAD9JG39Wo_CAQ1LZ9voQ"
},
"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=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTY5NTEyOTA0NH0.EiktFX7di_tBbspbjmqDKoWAD9JG39Wo_CAQ1LZ9voQ",
...
},
...
}

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.

Before proceeding, please ensure that the JWT signing endpoint has been exposed.

Save the key to an environment variable:

JACK_JWT_AUTH_KEY=jack-key

Create a consumer with jwt-auth plugin and reference the environment variable:

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

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
}
}
}'

Send a request to the signing endpoint to get JWT:

jwt_token=$(curl -s "http://127.0.0.1:9080/apisix/plugin/jwt/sign?key=jack-key") && echo $jwt_token

You should see a token similar to the following:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2OTUxMzMxNTUsImtleSI6Imp3dC1rZXkifQ.jiKuaAJqHNSSQCjXRomwnQXmdkC5Wp5VDPRsJlh1WAQ

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 similar to the following:

{
"args": {},
"headers": {
"Accept": "*/*",
"Authorization": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2OTUxMzMxNTUsImtleSI6Imp3dC1rZXkifQ.jiKuaAJqHNSSQCjXRomwnQXmdkC5Wp5VDPRsJlh1WAQ",
...
},
...
}

Manage Secrets in Secret Manager

The following example demonstrates how to manage jwt-auth consumer key in HashiCorp Vault and reference it in plugin configuration.

Start a Vault development server in Docker:

docker run -d \
--name vault \
-p 8200:8200 \
--cap-add IPC_LOCK \
-e VAULT_DEV_ROOT_TOKEN_ID=root \
-e VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:8200 \
vault:1.9.0 \
vault server -dev

APISIX currently supports Vault KV engine version 1. Enable it in Vault:

docker exec -i vault sh -c "VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' vault secrets enable -path=kv -version=1 kv"

You should see a response similar to the following:

Success! Enabled the kv secrets engine at: kv/

Create a secret and configure the Vault address and other connection information:

curl "http://127.0.0.1:9180/apisix/admin/secrets/vault/jwt" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"uri": "https://127.0.0.1:8200",
"prefix": "kv/apisix",
"token": "root"
}'

Create a consumer and reference the secret in the jwt-auth plugin:

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

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
}
}
}'

Set jwt-auth key value to be jwt-vault-key in Vault:

docker exec -i vault sh -c "VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' vault kv put kv/apisix/jack jwt-key=jwt-vault-key"

You should see a response similar to the following:

Success! Data written to: kv/apisix/jack

Before proceeding, please ensure that the JWT signing endpoint has been exposed.

Send a request to the signing endpoint to get JWT:

jwt_token=$(curl -s "http://127.0.0.1:9080/apisix/plugin/jwt/sign?key=jwt-vault-key") && echo $jwt_token

You should see a token similar to the following:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqd3QtdmF1bHQta2V5IiwiZXhwIjoxNjk1MTM4NjM1fQ.Au2liSZ8eQXUJR3SJESwNlIfqZdNyRyxIJK03L4dk_g

Sending request with the token as header:

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

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

{
"args": {},
"headers": {
"Accept": "*/*",
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqd3QtdmF1bHQta2V5IiwiZXhwIjoxNjk1MTM4NjM1fQ.Au2liSZ8eQXUJR3SJESwNlIfqZdNyRyxIJK03L4dk_g",
...
},
...
}

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 debugger and do the following:

  • Select RS256 in the Algorithm dropdown.
  • Copy and paste the key content into the Verify Signature section.
  • Update the payload with key matching the consumer key you would like to use; and exp or nbf in UNIX timestamp.

The configuration should look similar to the following:


complete configuration of JWT generation on jwt.io

Copy the JWT on the left and save to an environment variable:

jwt_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsImV4cCI6MTczNDIzMDQwMH0.XjqM0oszmCggwZs-8PUIlJv8wPJON1la2ET5v70E6TCE32Yq5ibrl-1azaK7IreAer3HtnVHeEfII2rR02v8xfR1TPIjU_oHov4qC-A4tLTbgqGVXI7fCy2WFm3PFh6MEKuRe6M3dCQtCAdkRRQrBr1gWFQZhV3TNeMmmtyIfuJpB7cp4DW5pYFsCcoE1Nw6Tz7dt8k0tPBTPI2Mv9AYfMJ30LHDscOaPNtz8YIk_TOkV9b9mhQudUJ7J_suCZMRxD3iL655jTp2gKsstGKdZa0_W9Reu4-HY3LSc5DS1XtfjuftpuUqgg9FvPU0mK_b0wT_Rq3lbYhcHb9GZ72qiQ

Create a consumer with jwt-auth plugin:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jack",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"algorithm": "RS256",
"public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnE0h4k/GWfEbYO/yE2MPjHtNKDLNz4mv1KNIPLxY2ccjPYOtjuug+iZ4MujLV59YfrHriTs0H8jweQfff3pRSMjyEK+4qWTY3TeKBXIEa3pVDeoedSJrgjLBVio6xH7et8ir+QScScfLaJHGB4/l3DDGyEhO782a9teY8brn5hsWX5uLmDJvxtTGAHYi847XOcx2UneW4tZ8wQ6JGBSiSg5qAHan4dFZ7CpixCNNqEcSK6EQ7lKOLeFGG8ys/dHBIEasU4oMlCuJH77+XQQ/shchy+vm9oZfP+grLZkV+nKAd8MQZsid7ZJ/fiB/BmnhGrjtIfh98jwxSx4DgdLhdwIDAQAB\n-----END PUBLIC KEY-----",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCcTSHiT8ZZ8Rtg7/ITYw+Me00oMs3Pia/Uo0g8vFjZxyM9g62O66D6Jngy6MtXn1h+seuJOzQfyPB5B99/elFIyPIQr7ipZNjdN4oFcgRrelUN6h51ImuCMsFWKjrEft63yKv5BJxJx8tokcYHj+XcMMbISE7vzZr215jxuufmGxZfm4uYMm/G1MYAdiLzjtc5zHZSd5bi1nzBDokYFKJKDmoAdqfh0VnsKmLEI02oRxIroRDuUo4t4UYbzKz90cEgRqxTigyUK4kfvv5dBD+yFyHL6+b2hl8/6CstmRX6coB3wxBmyJ3tkn9+IH8GaeEauO0h+H3yPDFLHgOB0uF3AgMBAAECggEARpY68Daw0Funzq5uN70r/3iLztSqx8hZpQEclXlF8wwQ6S33iqz1JSOMcwlZE7g9wfHd+jrHfndDypT4pVx7KxC86TZCghWuLrFvXqgwQM2dbcxGdwXVYZZEZAJsSeM19+/jYnFnl5ZoUVBMC4w79aX9j+O/6mKDUmjphHmxUuRCFjN0w7BRoYwmS796rSf1eoOcSXh2G9Ycc34DUFDfGpOzabndbmMfOz7W0DyUBG23fgLhNChTUGq8vMaqKXkQ8JKeKdEugSmRGz42HxjWoNlIGBDyB8tPNPT6SXsu/JBskdf9Gb71OWiub381oXC259sz+1K1REb1KSkgyC+bkQKBgQDKCnwXaf8aOIoJPCG53EqQfKScCIYQrvp1Uk3bs5tfYN4HcI3yAUnOqQ3Ux3eY9PfS37urlJXCfCbCnZ6P6xALZnN+aL2zWvZArlHvD6vnXiyevwK5IY+o2EW02h3A548wrGznQSsfX0tum22bEVlRuFfBbpZpizXwrV4ODSNhTwKBgQDGC27QQxah3yq6EbOhJJlJegjawVXEaEp/j4fD3qe/unLbUIFvCz6j9BAbgocDKzqXxlpTtIbnsesdLo7KM3MtYL0XO/87HIsBj9XCVgMkFCcM6YZ6fHnkJl0bs3haU4N9uI/wpokvfvXJp7iC9LUCseBdBj+N6T230HWiSbPjWQKBgQC8zzGKO/8vRNkSqkQmSczQ2/qE6p5G5w6eJy0lfOJdLswvDatJFpUf8PJA/6svoPYb9gOO5AtUNeuPAfeVLSnQTYzu+/kTrJTme0GMdAvE60gtjfmAgvGa64mw6gjWJk+1P92B+2/OIKMAmXXDbWIYMXqpBKzBs1vUMF/uJ68BlwKBgQDEivQem3YKj3/HyWmLstatpP7EmrqTgSzuC3OhX4b7L/5sySirG22/KKgTpSZ4bp5noeJiz/ZSWrAK9fmfkg/sKOV/+XsDHwCVPDnX86SKWbWnitp7FK2jTq94nlQC0H7edhvjqGLdUBJ9XoYu8MvzMLSJnXnVTHSDx832kU6FgQKBgQCbw4Eiu2IcOduIAokmsZl8Smh9ZeyhP2B/UBa1hsiPKQ6bw86QJr2OMbRXLBxtx+HYIfwDo4vXEE862PfoQyu6SjJBNmHiid7XcV06Z104UQNjP7IDLMMF+SASMqYoQWg/5chPfxBgIXnfWqw6TMmND3THY4Oj4Nhf4xeUg3HsaA==\n-----END PRIVATE KEY-----"
}
}
}'

❶ Configure the consumer key to be jack-key.

❷ Configure the JWT signing algorithm to be RS256.

❸ Configure the RSA public key.

❹ Configure the RSA private key.

tip

You should add a newline character after the opening line and before the closing line, for example -----BEGIN PRIVATE KEY-----\n......\n-----END PRIVATE 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 similar to the following:

{
"headers": {
"Accept": "*/*",
"Authorization": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsImV4cCI6MTczNDIzMDQwMH0.XjqM0oszmCggwZs-8PUIlJv8wPJON1la2ET5v70E6TCE32Yq5ibrl-1azaK7IreAer3HtnVHeEfII2rR02v8xfR1TPIjU_oHov4qC-A4tLTbgqGVXI7fCy2WFm3PFh6MEKuRe6M3dCQtCAdkRRQrBr1gWFQZhV3TNeMmmtyIfuJpB7cp4DW5pYFsCcoE1Nw6Tz7dt8k0tPBTPI2Mv9AYfMJ30LHDscOaPNtz8YIk_TOkV9b9mhQudUJ7J_suCZMRxD3iL655jTp2gKsstGKdZa0_W9Reu4-HY3LSc5DS1XtfjuftpuUqgg9FvPU0mK_b0wT_Rq3lbYhcHb9GZ72qiQ",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66c554fb-40c565462791cf6225016e6d",
"X-Forwarded-Host": "127.0.0.1"
}
}

API7.ai Logo

API Management for Modern Architectures with Edge, API Gateway, Kubernetes, and Service Mesh.

Product

API7 Cloud

SOC2 Type IRed Herring

Copyright © APISEVEN Ltd. 2019 – 2024. Apache, Apache APISIX, APISIX, and associated open source project names are trademarks of the

Apache Software Foundation