a7-plugin-jwt-auth
Overview
The jwt-auth plugin authenticates requests using JSON Web Tokens. Consumers
register a key and secret (or public key for asymmetric algorithms). Clients
include a signed JWT in the request header, query parameter, or cookie. API7 EE
validates the signature and claims, then forwards the request with consumer
identity headers.
When to Use
- Token-based stateless authentication
- Asymmetric key verification (RS256, ES256, EdDSA) where API7 EE only needs the public key
- Custom claims-based consumer identification
- Integration with external token issuers (your own auth server, Auth0, etc.)
Consumer Credential Reference
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
key | string | Yes | — | Unique identifier in JWT payload to match consumer |
secret | string | Conditional | — | Shared secret for HMAC algorithms (HS256/HS384/HS512). Encrypted in the database. |
public_key | string | Conditional | — | PEM public key for RSA/ECDSA/EdDSA algorithms |
algorithm | string | No | "HS256" | Signing algorithm (see supported list below) |
exp | integer | No | 86400 | Token lifetime in seconds (not UNIX timestamp) |
base64_secret | boolean | No | false | Set true if secret is base64-encoded |
lifetime_grace_period | integer | No | 0 | Clock skew tolerance in seconds |
Supported Algorithms
| Family | Algorithms |
|---|---|
| HMAC | HS256, HS384, HS512 |
| RSA | RS256, RS384, RS512 |
| RSA-PSS | PS256, PS384, PS512 |
| ECDSA | ES256, ES384, ES512 |
| EdDSA | EdDSA |
Route/Service Configuration Reference
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
header | string | No | "authorization" | Header to extract JWT from |
query | string | No | "jwt" | Query parameter to extract JWT from |
cookie | string | No | "jwt" | Cookie to extract JWT from |
hide_credentials | boolean | No | false | Remove JWT before forwarding upstream |
key_claim_name | string | No | "key" | JWT claim containing the consumer key |
anonymous_consumer | string | No | — | Consumer for unauthenticated requests |
claims_to_verify | array | No | — | Claims to require and verify (exp, nbf). Set this explicitly because behavior when omitted varies by gateway version. |
Token Lookup Priority
- Header (default:
authorization) — supportsBearer <token>prefix - Query parameter (default:
jwt) - Cookie (default:
jwt)
Step-by-Step: Enable jwt-auth with HS256
Replace <gateway-group-id> with the ID returned by
a7 gateway-group list -o json.
1. Create a consumer
a7 consumer create -g <gateway-group-id> -f - <<'EOF'
{
"username": "alice"
}
EOF
2. Add jwt-auth credential
a7 credential create cred-alice-jwt -g <gateway-group-id> \
--consumer alice \
--plugins-json '{"jwt-auth":{"key":"alice-key","secret":"alice-secret-minimum-32-chars-long","algorithm":"HS256","exp":86400}}'
3. Create a service and route with jwt-auth
a7 service create -g <gateway-group-id> -f - <<'EOF'
{
"id": "jwt-protected-service",
"name": "JWT protected service",
"upstream": {
"type": "roundrobin",
"nodes": [{"host": "backend", "port": 8080, "weight": 1}]
}
}
EOF
a7 route create -g <gateway-group-id> -f - <<'EOF'
{
"id": "jwt-protected",
"paths": ["/api/*"],
"service_id": "jwt-protected-service",
"plugins": {
"jwt-auth": {}
}
}
EOF
4. Generate a JWT and test
Create a JWT with payload {"key": "alice-key", "exp": <future_timestamp>}
signed with alice-secret-minimum-32-chars-long using HS256.
curl -i http://127.0.0.1:9080/api/test \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."
Step-by-Step: Enable jwt-auth with RS256
1. Generate RSA key pair
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
2. Create a consumer
a7 consumer create -g <gateway-group-id> -f - <<'EOF'
{
"username": "bob"
}
EOF
3. Create a credential with the public key
Save the following as bob-rs256-credential.yaml, replacing the placeholder
with the base64 body between the PEM delimiters in public.pem:
plugins:
jwt-auth:
key: bob-key
algorithm: RS256
public_key: |
-----BEGIN PUBLIC KEY-----
replace-with-the-base64-body-from-public.pem
-----END PUBLIC KEY-----
a7 credential create cred-bob-jwt -g <gateway-group-id> --consumer bob -f bob-rs256-credential.yaml
Keep the private key outside API7 Gateway.
Sign tokens with private.pem externally. API7 EE only needs the public key.
Common Patterns
Custom claim name (use iss instead of key)
# Credential config (the key value identifies the consumer):
{
"jwt-auth": {
"key": "my-issuer-id",
"secret": "my-secret"
}
}
# Route config:
{
"jwt-auth": {
"key_claim_name": "iss"
}
}
# JWT payload:
{
"iss": "my-issuer-id",
"exp": 1879318541
}
Clock skew tolerance
{
"jwt-auth": {
"key": "consumer-key",
"secret": "my-secret",
"lifetime_grace_period": 30
}
}
Allows 30 seconds clock drift between token issuer and API7 EE.
Token in query parameter
{
"plugins": {
"jwt-auth": {
"query": "token"
}
}
}
Client sends: curl "http://127.0.0.1:9080/api/test?token=eyJ..."
Secret management with environment variables
{
"jwt-auth": {
"key": "consumer-key",
"secret": "$env://JWT_SECRET"
}
}
Secret management with HashiCorp Vault
{
"jwt-auth": {
"key": "consumer-key",
"secret": "$secret://vault/jwt/consumer-name/jwt-secret"
}
}
Headers Added to Upstream
| Header | Value |
|---|---|
X-Consumer-Username | Consumer's username |
X-Credential-Identifier | Credential ID |
X-Consumer-Custom-Id | Consumer's labels.custom_id (if set) |
Error Responses
| HTTP Code | Message | Cause |
|---|---|---|
| 401 | "Missing JWT token in request" | No token in header/query/cookie |
| 401 | "JWT token invalid" | Malformed token |
| 401 | "failed to verify jwt" | Bad signature, expired, or invalid claims |
| 401 | "Invalid user key in JWT token" | Consumer key not found |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
401 "failed to verify jwt" | Token expired while exp verification is enabled | Generate new token with future exp |
401 "failed to verify jwt" | Algorithm mismatch | Ensure credential algorithm matches token |
401 "Invalid user key" | Wrong claim name | Set key_claim_name on the route or service and include that claim in the JWT |
| Public key rejected | Missing newlines in PEM | Include \n after header/before footer lines |
| Clock skew errors | Time drift | Set lifetime_grace_period on credential |
Config Sync Example
Save the following as jwt-auth.yaml:
version: "1"
services:
- id: jwt-protected-service
name: JWT protected service
upstream:
type: roundrobin
nodes:
- host: backend
port: 8080
weight: 1
routes:
- id: jwt-protected
name: JWT protected route
paths:
- /api/*
service_id: jwt-protected-service
plugins:
jwt-auth: {}
Validate and apply this partial configuration to the target gateway group:
a7 config validate -f jwt-auth.yaml
a7 config sync -g <gateway-group-id> -f jwt-auth.yaml --delete=false
Note: Create the consumer and credential separately with
a7 consumer createanda7 credential create. Config Sync manages only the service and route in this example. Disabling deletion preserves other resources that are not included in this partial configuration.
This page is generated from a7-plugin-jwt-auth/SKILL.md in the api7/a7 repository. Browse all skills on the AI Agent Skills page.