Skip to content

Latest commit

 

History

History
1679 lines (1372 loc) · 43.7 KB

File metadata and controls

1679 lines (1372 loc) · 43.7 KB
title jwt-auth
keywords
Apache APISIX
API Gateway
Plugin
JWT Auth
jwt-auth
description The jwt-auth Plugin supports the use of JSON Web Token (JWT) as a mechanism for clients to authenticate themselves before accessing Upstream resources.

import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';

Description

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-Identifier, 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.

Attributes

The following attributes are available for configurations on Consumers or Credentials.

Name Type Required Default Valid values Description
key string True non-empty A unique key that identifies the credential for a Consumer.
secret string False non-empty Shared key used to sign and verify the JWT when the algorithm is symmetric. Required when using HS256, HS384, or HS512 as the algorithm. This field supports saving the value in Secret Manager using the APISIX Secret resource.
public_key string False Public key in PEM format required by the configured asymmetric algorithm. Required if the algorithm is RS256, ES256, RS384, RS512, ES384, ES512, PS256, PS384, PS512, or EdDSA. This field supports saving the value in Secret Manager using the APISIX Secret resource.
algorithm string False HS256 HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512, EdDSA Encryption algorithm.
exp integer False 86400 >=1 Expiry time of the token in seconds. If you are not using APISIX to sign the JWT, this parameter is ignored and you should specify the expiration in the payload when signing the JWT.
base64_secret boolean False false Set to true if the secret is base64 encoded.
lifetime_grace_period integer False 0 >=0 Grace period in seconds. Used to account for clock skew between the server generating the JWT and the server validating the JWT.

NOTE: encrypt_fields = {"secret"} is also defined in the schema, which means that the field will be stored encrypted in etcd. See encrypted storage fields.

The following attributes are available for configurations on Routes or Services.

Name Type Required Default Valid values Description
header string False authorization The header to get the token from.
query string False jwt The query string to get the token from. Lower priority than header.
cookie string False jwt The cookie to get the token from. Lower priority than query.
hide_credentials boolean False false If true, do not pass the header, query, or cookie with JWT to Upstream services.
anonymous_consumer string False Anonymous Consumer name. If configured, allow anonymous users to bypass the authentication.
claims_to_verify array[string] False ["exp", "nbf"] combination of exp and nbf Specify the JWT claim(s) to verify, to ensure that the token is used within its allowed timeframe. Note that this is not the claims required to be presented in the payload, but the claims to verify, if presented.
store_in_ctx boolean False false If true, store JWT payload in the request context variable ctx.jwt_auth_payload. This allows plugins executed after jwt-auth on the same request to retrieve and use the payload information.
realm string False jwt The realm to include in the WWW-Authenticate header when authentication fails.
key_claim_name string False key The claim in the JWT payload that identifies the associated secret, such as iss.

You can implement jwt-auth with HashiCorp Vault to store and fetch secrets and RSA key pairs from its encrypted KV engine using the APISIX Secret resource.

Examples

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

:::note

You can fetch the admin_key from conf/config.yaml and save to an environment variable with the following command:

admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')

:::

Use JWT for Consumer Authentication

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

<Tabs groupId="api" defaultValue="dashboard" values={[ {label: 'Admin API', value: 'dashboard'}, {label: 'ADC', value: 'adc'}, {label: 'Ingress Controller', value: 'ingress'} ]}>

Create a Consumer jack:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
  -H "X-API-KEY: ${admin_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_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_key}" \
  -d '{
    "id": "jwt-route",
    "uri": "/headers",
    "plugins": {
      "jwt-auth": {}
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'

Create a Consumer with jwt-auth Credential and a Route with jwt-auth Plugin configured as such:

consumers:
  - username: jack
    credentials:
      - name: jwt-auth
        type: jwt-auth
        config:
          key: jack-key
          secret: jack-hs256-secret-that-is-very-long
services:
  - name: jwt-auth-service
    routes:
      - name: jwt-route
        uris:
          - /headers
        plugins:
          jwt-auth: {}
    upstream:
      type: roundrobin
      nodes:
        - host: httpbin.org
          port: 80
          weight: 1

Synchronize the configuration to the gateway:

adc sync -f adc.yaml

<Tabs groupId="k8s-api" defaultValue="gateway-api" values={[ {label: 'Gateway API', value: 'gateway-api'}, {label: 'APISIX Ingress Controller', value: 'apisix-ingress-controller'} ]}>

Create a Consumer with jwt-auth Credential and a Route with jwt-auth Plugin configured as such:

apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
  namespace: aic
  name: jack
spec:
  gatewayRef:
    name: apisix
  credentials:
    - type: jwt-auth
      name: primary-cred
      config:
        key: jack-key
        secret: jack-hs256-secret-that-is-very-long
---
apiVersion: v1
kind: Service
metadata:
  namespace: aic
  name: httpbin-external-domain
spec:
  type: ExternalName
  externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
  namespace: aic
  name: jwt-auth-plugin-config
spec:
  plugins:
    - name: jwt-auth
      config:
        _meta:
          disable: false
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  namespace: aic
  name: jwt-route
spec:
  parentRefs:
    - name: apisix
  rules:
    - matches:
        - path:
            type: Exact
            value: /headers
      filters:
        - type: ExtensionRef
          extensionRef:
            group: apisix.apache.org
            kind: PluginConfig
            name: jwt-auth-plugin-config
      backendRefs:
        - name: httpbin-external-domain
          port: 80

Apply the configuration to your cluster:

kubectl apply -f jwt-auth-ic.yaml

The ApisixConsumer CRD has a known issue where private_key is incorrectly required during the configuration. This issue will be addressed in a future release. At the moment, the example cannot be completed with APISIX CRDs.

To issue a JWT for jack, you could use JWT.io's JWT encoder or other utilities. If you are using JWT.io's JWT encoder, 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.

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

Carry JWT in Request Header, Query String, or Cookie

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

<Tabs groupId="api" defaultValue="dashboard" values={[ {label: 'Admin API', value: 'dashboard'}, {label: 'ADC', value: 'adc'}, {label: 'Ingress Controller', value: 'ingress'} ]}>

Create a Consumer jack:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
  -H "X-API-KEY: ${admin_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_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_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
      }
    }
  }'

Create a Consumer with jwt-auth Credential and a Route with jwt-auth Plugin configured as such:

consumers:
  - username: jack
    credentials:
      - name: jwt-auth
        type: jwt-auth
        config:
          key: jack-key
          secret: jack-hs256-secret-that-is-very-long
services:
  - name: jwt-auth-service
    routes:
      - name: jwt-route
        uris:
          - /get
        plugins:
          jwt-auth:
            header: jwt-auth-header
            query: jwt-query
            cookie: jwt-cookie
    upstream:
      type: roundrobin
      nodes:
        - host: httpbin.org
          port: 80
          weight: 1

Synchronize the configuration to the gateway:

adc sync -f adc.yaml

<Tabs groupId="k8s-api" defaultValue="gateway-api" values={[ {label: 'Gateway API', value: 'gateway-api'}, {label: 'APISIX Ingress Controller', value: 'apisix-ingress-controller'} ]}>

Create a Consumer with jwt-auth Credential and a Route with jwt-auth Plugin configured as such:

apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
  namespace: aic
  name: jack
spec:
  gatewayRef:
    name: apisix
  credentials:
    - type: jwt-auth
      name: primary-cred
      config:
        key: jack-key
        secret: jack-hs256-secret-that-is-very-long
---
apiVersion: v1
kind: Service
metadata:
  namespace: aic
  name: httpbin-external-domain
spec:
  type: ExternalName
  externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
  namespace: aic
  name: jwt-auth-plugin-config
spec:
  plugins:
    - name: jwt-auth
      config:
        _meta:
          disable: false
        header: jwt-auth-header
        query: jwt-query
        cookie: jwt-cookie
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  namespace: aic
  name: jwt-route
spec:
  parentRefs:
    - name: apisix
  rules:
    - matches:
        - path:
            type: Exact
            value: /get
      filters:
        - type: ExtensionRef
          extensionRef:
            group: apisix.apache.org
            kind: PluginConfig
            name: jwt-auth-plugin-config
      backendRefs:
        - name: httpbin-external-domain
          port: 80

Apply the configuration to your cluster:

kubectl apply -f jwt-auth-ic.yaml

The ApisixConsumer CRD has a known issue where private_key is incorrectly required during the configuration. This issue will be addressed in a future release. At the moment, the example cannot be completed with APISIX CRDs.

To issue a JWT for jack, you could use JWT.io's JWT encoder or other utilities. If you are using JWT.io's JWT encoder, 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.

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

Verify With JWT in Cookie

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. If you are running APISIX in Docker, you should set the environment variable using the -e flag when starting the container.

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

<Tabs groupId="api" defaultValue="dashboard" values={[ {label: 'Admin API', value: 'dashboard'}, {label: 'ADC', value: 'adc'} ]}>

Create a Consumer jack:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
  -H "X-API-KEY: ${admin_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_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_key}" \
  -d '{
    "id": "jwt-route",
    "uri": "/get",
    "plugins": {
      "jwt-auth": {}
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'

Create a Consumer with jwt-auth Credential referencing an environment variable and a Route with jwt-auth Plugin enabled as such:

consumers:
  - username: jack
    credentials:
      - name: jwt-auth
        type: jwt-auth
        config:
          key: jack-key
          secret: $env://JACK_JWT_SECRET
services:
  - name: jwt-auth-service
    routes:
      - name: jwt-route
        uris:
          - /get
        plugins:
          jwt-auth: {}
    upstream:
      type: roundrobin
      nodes:
        - host: httpbin.org
          port: 80
          weight: 1

Synchronize the configuration to the gateway:

adc sync -f adc.yaml

To issue a JWT for jack, you could use JWT.io's JWT encoder or other utilities. If you are using JWT.io's JWT encoder, 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.

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.

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/

<Tabs groupId="api" defaultValue="dashboard" values={[ {label: 'Admin API', value: 'dashboard'}, {label: 'ADC', value: 'adc'} ]}>

Create a Secret and configure the Vault address and other connection information. Adjust the Vault address accordingly:

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

Create a Consumer jack:

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

Create jwt-auth Credential for the Consumer and reference the Secret:

curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "cred-jack-jwt-auth",
    "plugins": {
      "jwt-auth": {
        "key": "jwt-vault-key",
        "secret": "$secret://vault/jwt/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_key}" \
  -d '{
    "id": "jwt-route",
    "uri": "/get",
    "plugins": {
      "jwt-auth": {}
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'

Create a Secret and configure the Vault address. Adjust the Vault address accordingly:

secrets:
  - name: vault-jwt
    vault:
      url: http://127.0.0.1:8200
      prefix: kv/apisix
      token: root
consumers:
  - username: jack
    credentials:
      - name: jwt-auth
        type: jwt-auth
        config:
          key: jwt-vault-key
          secret: $secret://vault-jwt/jack/jwt-secret
services:
  - name: jwt-auth-service
    routes:
      - name: jwt-route
        uris:
          - /get
        plugins:
          jwt-auth: {}
    upstream:
      type: roundrobin
      nodes:
        - host: httpbin.org
          port: 80
          weight: 1

Synchronize the configuration to the gateway:

adc sync -f adc.yaml

Set jwt-auth key value to be vault-hs256-secret-that-is-very-long 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-secret=vault-hs256-secret-that-is-very-long"

You should see a response similar to the following:

Success! Data written to: kv/apisix/jack

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

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

Your payload should look similar to the following:

{
  "key": "jwt-vault-key",
  "nbf": 1729132271
}

Copy the generated JWT and save to a variable:

export jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqd3QtdmF1bHQta2V5IiwibmJmIjoxNzI5MTMyMjcxfQ.i2pLj7QcQvnlSjB7iV5V522tIV43boQRtee7L0rwlkQ

Send a request with the token 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 encoder 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
}

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

<Tabs groupId="api" defaultValue="dashboard" values={[ {label: 'Admin API', value: 'dashboard'}, {label: 'ADC', value: 'adc'}, {label: 'Ingress Controller', value: 'ingress'} ]}>

Create a Consumer jack:

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

Create jwt-auth Credential for the Consumer and configure the RSA keys. You should add a newline character after the opening line and before the closing line of the public key, for example -----BEGIN PUBLIC KEY-----\n......\n-----END PUBLIC KEY-----. The key content can be directly concatenated.

curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
  -H "X-API-KEY: ${admin_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-----"
      }
    }
  }'

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_key}" \
  -d '{
    "id": "jwt-route",
    "uri": "/headers",
    "plugins": {
      "jwt-auth": {}
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'

Create a Consumer with jwt-auth Credential using RS256 algorithm and a Route with jwt-auth Plugin enabled as such:

consumers:
  - username: jack
    credentials:
      - name: jwt-auth
        type: jwt-auth
        config:
          key: jack-key
          algorithm: RS256
          public_key: |
            -----BEGIN PUBLIC KEY-----
            MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoTxe7ZPycrEP0SK4OBA2
            0OUQsDN9gSFSHVvx/t++nZNrFxzZnV6q6/TRsihNXUIgwaOu5icFlIcxPL9Mf9UJ
            a5/XCQExp1TxpuSmjkhIFAJ/x5zXrC8SGTztP3SjkhYnQO9PKVXI6ljwgakVCfpl
            umuTYqI+ev7e45NdK8gJoJxPp8bPMdf8/nHfLXZuqhO/btrDg1x+j7frDNrEw+6B
            CK2SsuypmYN+LwHfaH4Of7MQFk3LNIxyBz0mdbsKJBzp360rbWnQeauWtDymZxLT
            ATRNBVyl3nCNsURRTkc7eyknLaDt2N5xTIoUGHTUFYSdE68QWmukYMVGcEHEEPkp
            aQIDAQAB
            -----END PUBLIC KEY-----
services:
  - name: jwt-auth-service
    routes:
      - name: jwt-route
        uris:
          - /headers
        plugins:
          jwt-auth: {}
    upstream:
      type: roundrobin
      nodes:
        - host: httpbin.org
          port: 80
          weight: 1

Synchronize the configuration to the gateway:

adc sync -f adc.yaml

<Tabs groupId="k8s-api" defaultValue="gateway-api" values={[ {label: 'Gateway API', value: 'gateway-api'}, {label: 'APISIX Ingress Controller', value: 'apisix-ingress-controller'} ]}>

Create a Consumer with jwt-auth Credential using RS256 algorithm and a Route with jwt-auth Plugin enabled as such:

apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
  namespace: aic
  name: jack
spec:
  gatewayRef:
    name: apisix
  credentials:
    - type: jwt-auth
      name: primary-cred
      config:
        key: jack-key
        algorithm: RS256
        public_key: |
          -----BEGIN PUBLIC KEY-----
          MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoTxe7ZPycrEP0SK4OBA2
          0OUQsDN9gSFSHVvx/t++nZNrFxzZnV6q6/TRsihNXUIgwaOu5icFlIcxPL9Mf9UJ
          a5/XCQExp1TxpuSmjkhIFAJ/x5zXrC8SGTztP3SjkhYnQO9PKVXI6ljwgakVCfpl
          umuTYqI+ev7e45NdK8gJoJxPp8bPMdf8/nHfLXZuqhO/btrDg1x+j7frDNrEw+6B
          CK2SsuypmYN+LwHfaH4Of7MQFk3LNIxyBz0mdbsKJBzp360rbWnQeauWtDymZxLT
          ATRNBVyl3nCNsURRTkc7eyknLaDt2N5xTIoUGHTUFYSdE68QWmukYMVGcEHEEPkp
          aQIDAQAB
          -----END PUBLIC KEY-----
---
apiVersion: v1
kind: Service
metadata:
  namespace: aic
  name: httpbin-external-domain
spec:
  type: ExternalName
  externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
  namespace: aic
  name: jwt-auth-plugin-config
spec:
  plugins:
    - name: jwt-auth
      config:
        _meta:
          disable: false
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  namespace: aic
  name: jwt-route
spec:
  parentRefs:
    - name: apisix
  rules:
    - matches:
        - path:
            type: Exact
            value: /headers
      filters:
        - type: ExtensionRef
          extensionRef:
            group: apisix.apache.org
            kind: PluginConfig
            name: jwt-auth-plugin-config
      backendRefs:
        - name: httpbin-external-domain
          port: 80

Apply the configuration to your cluster:

kubectl apply -f jwt-auth-ic.yaml

The ApisixConsumer CRD has a known issue where private_key is incorrectly required during the configuration. This issue will be addressed in a future release. At the moment, the example cannot be completed with APISIX CRDs.

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 X-Consumer-Custom-ID header, which can be used to implement additional logics as needed.

<Tabs groupId="api" defaultValue="dashboard" values={[ {label: 'Admin API', value: 'dashboard'}, {label: 'ADC', value: 'adc'}, {label: 'Ingress Controller', value: 'ingress'} ]}>

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_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_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_key}" \
  -d '{
    "id": "jwt-auth-route",
    "uri": "/anything",
    "plugins": {
      "jwt-auth": {}
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'

Create a Consumer with jwt-auth Credential and a Route with jwt-auth Plugin enabled as such:

consumers:
  - username: jack
    labels:
      custom_id: "495aec6a"
    credentials:
      - name: jwt-auth
        type: jwt-auth
        config:
          key: jack-key
          secret: jack-hs256-secret-that-is-very-long
services:
  - name: jwt-auth-service
    routes:
      - name: jwt-auth-route
        uris:
          - /anything
        plugins:
          jwt-auth: {}
    upstream:
      type: roundrobin
      nodes:
        - host: httpbin.org
          port: 80
          weight: 1

Synchronize the configuration to the gateway:

adc sync -f adc.yaml

Consumer custom labels are currently not supported when configuring resources through the Ingress Controller, and the X-Consumer-Custom-Id header is not included in requests. At the moment, this example cannot be completed with the Ingress Controller.

To issue a JWT for jack, you could use JWT.io's JWT encoder or other utilities. If you are using JWT.io's JWT encoder, 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.

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

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.

<Tabs groupId="api" defaultValue="dashboard" values={[ {label: 'Admin API', value: 'dashboard'}, {label: 'ADC', value: 'adc'}, {label: 'Ingress Controller', value: 'ingress'} ]}>

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_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_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_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_key}" \
  -d '{
    "id": "jwt-auth-route",
    "uri": "/anything",
    "plugins": {
      "jwt-auth": {
        "anonymous_consumer": "anonymous"
      }
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'

Configure Consumers with different rate limits and a Route that accepts anonymous users:

consumers:
  - username: jack
    plugins:
      limit-count:
        count: 3
        time_window: 30
        rejected_code: 429
        policy: local
    credentials:
      - name: jwt-auth
        type: jwt-auth
        config:
          key: jack-key
          secret: jack-hs256-secret-that-is-very-long
  - username: anonymous
    plugins:
      limit-count:
        count: 1
        time_window: 30
        rejected_code: 429
        policy: local
services:
  - name: anonymous-rate-limit-service
    routes:
      - name: jwt-auth-route
        uris:
          - /anything
        plugins:
          jwt-auth:
            anonymous_consumer: anonymous
    upstream:
      type: roundrobin
      nodes:
        - host: httpbin.org
          port: 80
          weight: 1

Synchronize the configuration to the gateway:

adc sync -f adc.yaml

<Tabs groupId="k8s-api" defaultValue="gateway-api" values={[ {label: 'Gateway API', value: 'gateway-api'}, {label: 'APISIX Ingress Controller', value: 'apisix-ingress-controller'} ]}>

Configure Consumers with different rate limits and a Route that accepts anonymous users:

apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
  namespace: aic
  name: jack
spec:
  gatewayRef:
    name: apisix
  credentials:
    - type: jwt-auth
      name: primary-key
      config:
        key: jack-key
        secret: jack-hs256-secret-that-is-very-long
  plugins:
    - name: limit-count
      config:
        count: 3
        time_window: 30
        rejected_code: 429
        policy: local
---
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
  namespace: aic
  name: anonymous
spec:
  gatewayRef:
    name: apisix
  plugins:
    - name: limit-count
      config:
        count: 1
        time_window: 30
        rejected_code: 429
        policy: local
---
apiVersion: v1
kind: Service
metadata:
  namespace: aic
  name: httpbin-external-domain
spec:
  type: ExternalName
  externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
  namespace: aic
  name: jwt-auth-plugin-config
spec:
  plugins:
    - name: jwt-auth
      config:
        anonymous_consumer: aic_anonymous  # namespace_consumername
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  namespace: aic
  name: jwt-auth-route
spec:
  parentRefs:
    - name: apisix
  rules:
    - matches:
        - path:
            type: Exact
            value: /anything
      filters:
        - type: ExtensionRef
          extensionRef:
            group: apisix.apache.org
            kind: PluginConfig
            name: jwt-auth-plugin-config
      backendRefs:
        - name: httpbin-external-domain
          port: 80

Apply the configuration to your cluster:

kubectl apply -f jwt-auth-ic.yaml

The ApisixConsumer CRD currently does not support configuring plugins on Consumers, except for the authentication plugins allowed in authParameter. This example cannot be completed with APISIX CRDs.

To issue a JWT for jack, you could use JWT.io's JWT encoder or other utilities. If you are using JWT.io's JWT encoder, 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.

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