JWT Authentication in APISIX#
The jwt-auth plugin provides token-based authentication at the gateway layer. It sits at priority 2510 in the plugin execution order, running before upstream proxying. When a request arrives, the plugin extracts the JWT from the request, maps it to a Consumer via a claim lookup, verifies the signature against that Consumer's stored key material, and either passes or rejects the request — all without any upstream session state.
Primary source files:
- Implementation:
apisix/plugins/jwt-auth.lua - Official docs:
docs/en/latest/plugins/jwt-auth.md - API7 hub: docs.api7.ai/hub/jwt-auth
Consumer Model and Credential Storage#
JWT credentials live on Consumers, not on routes. A route only needs "jwt-auth": {} — all key material is stored in the Consumer's credential object .
Each credential must include a unique key field. The plugin extracts this value from the JWT payload claim named key (configurable via key_claim_name) and looks up the matching Consumer . On success, APISIX injects X-Consumer-Username, X-Credential-Identifier, and optional custom label headers upstream .
Supported Algorithms#
The algorithm field on the Consumer credential controls verification mode :
| Algorithm | Key fields required | Notes |
|---|---|---|
HS256 (default) | secret | Shared HMAC key; auto-generated if omitted |
HS512 | secret | Shared HMAC key |
RS256 | public_key | RSA asymmetric; secret must not be set |
ES256 | public_key | ECDSA asymmetric; secret must not be set |
Schema enforcement: when RS256 or ES256 is selected, public_key becomes required and no secret is accepted . For symmetric algorithms, if secret is omitted, one is auto-generated .
Asymmetric Algorithms (RS256 / ES256) and External Identity Providers#
With asymmetric algorithms, APISIX only stores the public key on the Consumer — the private key never leaves the identity provider. This makes jwt-auth suitable for verifying tokens issued by external systems (e.g., Keycloak) without sharing secrets.
Workflow:
- The external IdP (e.g., Keycloak) issues a JWT signed with its RSA/ECDSA private key.
- Create an APISIX Consumer whose
jwt-authcredential hasalgorithm: RS256andpublic_keyset to the IdP's corresponding public key in PEM format . - Set
key_claim_nameon the route to whatever claim the IdP populates (sub,iss, etc.) and ensure a matching Consumerkeyexists for that value . - APISIX verifies the token's signature via get_auth_secret →
jwt:verify_jwt_obj.
Note:
jwt-authdoes not natively support JWKS endpoint discovery. To use a Keycloak JWKS endpoint for dynamic key rotation, use theopenid-connectplugin instead — it supports the OIDC discovery document anduse_jwksverification.
PEM key format: embed \n after the opening line and before the closing line; key bytes can be concatenated directly .
Token Location and Route-Level Config#
Token lookup order: header → query string → cookie . Route-level options :
| Field | Default | Purpose |
|---|---|---|
header | authorization | Header name to read token from |
query | jwt | Query param name |
cookie | jwt | Cookie name |
hide_credentials | false | Strip token before forwarding upstream |
key_claim_name | key | JWT claim used for Consumer lookup |
anonymous_consumer | — | Fallback Consumer if auth fails |
store_in_ctx | false | Expose payload to downstream plugins via ctx.jwt_auth_payload |
Bearer prefix in the Authorization header is stripped automatically .
Public Key and Secret Management#
Sensitive fields can be stored outside APISIX config:
- Environment variables: reference via
$env://VAR_NAMEin thesecretorpublic_keyfield . - HashiCorp Vault (KV v1): configure an APISIX Secret resource, then reference as
$secret://vault/<path>.
The secret field is stored encrypted in etcd via encrypt_fields = {"secret"} .
Claim Verification#
By default, APISIX validates exp and nbf via jwt:get_default_validation_options. The lifetime_grace_period field (seconds) accounts for clock skew between the token issuer and APISIX . The exp default on Consumer credentials is 86400 seconds .
Anonymous Consumer Fallback#
Setting anonymous_consumer on the route allows unauthenticated requests to be mapped to a specific Consumer instead of rejected . This is useful for tiered rate limiting — authenticated consumers get a higher quota, anonymous ones a lower quota .