JWT Authentication

Validate JSON Web Tokens on incoming requests. Supports RSA, ECDSA, and Ed25519 signatures with remote or inline JWKS. Extract claims into headers for upstream consumption.

Configuration

{
  "name": "auth",
  "type": "jwt",
  "jwt": {
    "issuer": "https://auth.example.com",
    "audiences": ["api"],
    "jwksPath": "/.well-known/jwks.json",
    "jwksDestinationId": "<auth-server-destination>",
    "jwksRetrievalTimeout": "10s",
    "forwardJwt": true,
    "claimToHeaders": [
      {"expr": "claims.sub", "header": "X-User-ID"},
      {"expr": "claims.roles[0]", "header": "X-User-Role"},
      {"expr": "claims.orgs.map(o, o.name).join(',')", "header": "X-User-Orgs"}
    ],
    "assertClaims": [
      "claims.org == 'acme'",
      "'admin' in claims.roles"
    ]
  }
}

All fields

FieldTypeDefaultDescription
issuerstringrequiredExpected iss claim value
audiencesstring[]Expected aud values (empty = skip audience check)
jwksPathstringHTTP path on the destination to fetch JWKS
jwksDestinationIdstringDestination hosting the JWKS endpoint
jwksInlinestringLiteral JWKS JSON string (alternative to remote)
jwksRetrievalTimeoutstring10sMax time to download JWKS from remote
forwardJwtboolfalseForward the Authorization header to upstream
claimToHeadersarrayExtract claims via CEL and inject as request headers
assertClaimsstring[]CEL expressions that must all return true

Signature algorithms

Algorithm familyAlgorithmsKey type
RSARS256, RS384, RS512RSA public key
ECDSAES256, ES384, ES512EC public key (P-256, P-384, P-521)
EdDSAEdDSAEd25519 public key

The algorithm is determined by the JWKS key, not by configuration.

Examples

Basic JWT validation (Auth0, Okta, Keycloak)

{
  "name": "auth",
  "type": "jwt",
  "jwt": {
    "issuer": "https://myapp.auth0.com/",
    "audiences": ["https://api.myapp.com"],
    "jwksPath": "/.well-known/jwks.json",
    "jwksDestinationId": "<auth0-destination>"
  }
}

The auth0-destination must be a Vrata destination pointing to your Auth0 domain. Vrata fetches JWKS from https://myapp.auth0.com/.well-known/jwks.json via that destination.

Forward JWT + extract user ID

{
  "name": "auth-with-user",
  "type": "jwt",
  "jwt": {
    "issuer": "https://auth.example.com",
    "audiences": ["api"],
    "jwksPath": "/.well-known/jwks.json",
    "jwksDestinationId": "<auth-dest>",
    "forwardJwt": true,
    "claimToHeaders": [
      {"expr": "claims.sub", "header": "X-User-ID"},
      {"expr": "claims.email", "header": "X-User-Email"}
    ]
  }
}

The upstream receives:

Claim assertions (RBAC)

{
  "name": "admin-auth",
  "type": "jwt",
  "jwt": {
    "issuer": "https://auth.example.com",
    "jwksPath": "/.well-known/jwks.json",
    "jwksDestinationId": "<auth-dest>",
    "assertClaims": [
      "claims.org == 'acme'",
      "'admin' in claims.roles",
      "claims.exp > now"
    ]
  }
}

All assertions must evaluate to true. If any fails → 403 Forbidden.

Complex claim extraction (CEL)

{
  "claimToHeaders": [
    {"expr": "claims.sub", "header": "X-User-ID"},
    {"expr": "claims.roles[0]", "header": "X-User-Role"},
    {"expr": "claims.roles.join(',')", "header": "X-User-Roles"},
    {"expr": "claims.orgs.map(o, o.name).join(',')", "header": "X-User-Orgs"},
    {"expr": "claims.metadata.tier", "header": "X-User-Tier"},
    {"expr": "string(claims.iat)", "header": "X-Token-Issued-At"}
  ]
}

CEL expressions can navigate nested claims, index arrays, call string methods, and transform values.

Inline JWKS (development / testing)

{
  "name": "dev-auth",
  "type": "jwt",
  "jwt": {
    "issuer": "dev",
    "jwksInline": "{\"keys\":[{\"kty\":\"RSA\",\"kid\":\"dev-key\",\"n\":\"...\",\"e\":\"AQAB\"}]}"
  }
}

No external JWKS endpoint needed. Useful for tests or when the auth server is on the same network.

Skip JWT on health endpoints

Attach the middleware to a group and skip it on specific paths:

{
  "middlewareOverrides": {
    "auth": {
      "skipWhen": ["request.path == '/health'", "request.path == '/ready'"]
    }
  }
}

Error responses

StatusWhen
401 UnauthorizedMissing token, invalid format, bad signature, expired, unknown key ID
403 ForbiddenToken is valid but a claim assertion failed

All errors return JSON:

{"error": "jwt: token expired"}