Running Tyk Gateway v5.11 in OAS mode. We’re trying to enforce a per-operation JWT claim check using URL rewrite trigger requestContextMatches, but we’re hitting what appears to be a hard limitation: context variables for JWT claims (jwt_claims_) are only populated for root-level string properties on the token. Any claim that is an object, array, or nested property appears to be empty/null in the request context.
Our JWT structure (simplified):
{
"iss": "https://example.cloudflareaccess.com",
"sub": "...",
"custom": {
"omig_api_roles": "media:read"
}
}
jwt_claims_custom is empty at trigger evaluation time - Tyk does not appear to serialize the object or flatten its properties into child context variables.
What we’ve tried:
- jwt_claims_custom → empty (object claim)
- jwt_claims_custom.omig_api_roles → not a valid context variable name
- jwt_claims_omig_api_roles → empty (not a root-level claim)
Stripped-down API definition:
Stripped-down API definition:
{
"openapi": "3.0.3",
"info": {
"title": "Media API - Retrieve recipient-based and file-based media files",
"version": "1.0.0"
},
"servers": [
{
"url": "http://testapi01.local:8080"
}
],
"paths": {
"/v1/file/{fileKey}/recips/{recipient}": {
"get": {
"operationId": "getRecipentFile",
"parameters": [
{
"name": "fileKey",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "recipient",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Retrieved"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
}
},
"x-tyk-api-gateway": {
"middleware": {}
}
}
},
"/v1/file/{fileKey}": {
"get": {
"operationId": "getFile",
"parameters": [
{
"name": "fileKey",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Retrieved"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
}
},
"x-tyk-api-gateway": {
"middleware": {}
}
}
},
"/internal/forbidden": {
"get": {
"operationId": "forbidden",
"responses": {
"403": {
"description": "Forbidden"
}
},
"x-tyk-api-gateway": {
"middleware": {}
}
}
}
},
"components": {
"securitySchemes": {
"auth0Jwt": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
}
}
},
"security": [
{
"auth0Jwt": []
}
],
"x-tyk-api-gateway": {
"info": {
"id": "media-api-dev",
"name": "Media API Dev",
"state": {
"active": true
}
},
"server": {
"listenPath": {
"value": "/media-dev/api",
"strip": true
},
"authentication": {
"enabled": true,
"securitySchemes": {
"auth0Jwt": {
"enabled": true,
"defaultPolicies": [
"auth0-default"
],
"header": {
"enabled": true,
"name": "Authorization"
},
"cookie": {
"enabled": true,
"name": "CF_Authorization"
},
"jwksURIs": [
{
"url": "https://jwksurl01/jwks.json"
},
{
"url": "https://jwksurl02/certs"
}
],
"allowedIssuers": [
"https://issuer01/",
"https://issuer02/"
],
"subjectClaims": [
"sub"
],
"allowedAudiences": [
"https://aud01",
"https://aud02",
"https://aud03"
],
"signingMethod": "rsa",
"jtiValidation": {
"enabled": false
}
}
}
}
},
"upstream": {
"url": "http://legacy-upstream"
},
"middleware": {
"global": {
"contextVariables": {
"enabled": true
},
"isOAS": true,
"transformRequestHeaders": {
"enabled": true,
"add": [
{
"name": "Authorization",
"value": "Basic secrets://api_dev_upstream_basic_creds"
}
],
"remove": []
},
"cache": {
"enabled": true
}
},
"operations": {
"getRecipentFile": {
"urlRewrite": {
"enabled": true,
"pattern": "/v1/file/(.*)/recips/(.*)",
"rewriteTo": "/mm-company-webservice/rest/reps/companyDap/docs/$1/recips/$2",
"triggers": [
{
"condition": "any",
"rewriteTo": "tyk://self/internal/forbidden",
"rules": [
{
"in": "requestContext",
"name": "jwt_claims_custom",
"pattern": "\"company_api_roles\":\"[^\"]*media:read",
"negate": true
}
]
}
]
}
},
"getFile": {
"urlRewrite": {
"enabled": true,
"pattern": "/v1/file/(.*)",
"rewriteTo": "/mm-company-webservice/rest/reps/companyDev/docs/$1/file",
"triggers": [
{
"condition": "any",
"rewriteTo": "tyk://self/internal/forbidden",
"rules": [
{
"in": "requestContext",
"name": "jwt_claims_custom",
"pattern": "\"company_api_roles\":\"[^\"]*media:read",
"negate": true
}
]
}
]
}
},
"forbidden": {
"internal": {
"enabled": true
},
"mockResponse": {
"enabled": true,
"code": 403,
"body": "{\"error\": \"missing required claim\"}"
}
}
}
}
}
}
Questions:
-
Is this a confirmed limitation - that requestContextMatches in URL rewrite triggers can only match root-level string JWT claims?
-
Is there any mechanism in Tyk OAS v5.x to access nested or object-type claim properties in trigger rules?
-
Is there an alternative approach for per-operation JWT claim enforcement in OAS that doesn’t require a Go plugin? (customClaimValidation appears to be API-wide only.)
Trying to determine whether this is a configuration gap on our end or an actual platform limitation before escalating.