Tyk + JWT HMAC validation error

Hello,
I can’t seem to get tyk to work with JWT using HMAC, I consistently get JWT validation error, even though the tokens are valid:

level=info msg=“Attempted JWT access with non-existent key.”
level=error msg=“JWT validation error: illegal base64 data at input byte 0”
level=error msg=“request error: Key not authorized” api_id=1 org_id=default

Tested using Postman with header “Authorization: Bearer: token…”, testing it directly on target host works without any problems (token gets validated by the target service).
Gateway version is 2.3.3

secret = wxIPF6lfvIQRhOG0z1A0QwxNRfdjOzgf

sample token:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwb2xpY3ktaWQiOiJkZWZhdWx0Iiwic3ViIjoxLCJpc3MiOiJodHRwOlwvXC8xMC4yNC4xMDEuNjk6ODA4MVwvdjFcL2F1dGhcL2F1dGhlbnRpY2F0ZSIsImlhdCI6MTQ5MDc2NTU3MSwiZXhwIjoxNDkwNzY5MTcxLCJuYmYiOjE0OTA3NjU1NzEsImp0aSI6IjViNDM3M2VlNjNjN2QzNTBlZjhmZDlhZmZkNTM4MzRlIn0.Q4BlPcK4zWp3tGTZINNKCsepZYq9xqjVgkZGrBWeGzo

 {
   typ: "JWT",
   alg: "HS256"
 }
 {
   policy-id: "default",
   sub: 1,
   iss: "http://10.24.101.69:8081/v1/auth/authenticate",
   iat: 1490765571,
   exp: 1490769171,
   nbf: 1490765571,
   jti: "5b4373ee63c7d350ef8fd9affd53834e"
 }
 Q4BlPcK4zWp3tGTZINNKCsepZYq9xqjVgkZGrBWeGzo

api definition is simple enough:

{
    "name": "test service",
    "api_id": "1",
    "org_id": "default",
    "definition": {
        "location": "header",
        "key": "version"
    },
    "auth": {
        "auth_header_name": "authorization"
    },
    "enable_jwt": true,
    "jwt_identity_base_field": "sub",
    "jwt_policy_field_name": "policy-id",
    "jwt_source": "d3hJUEY2bGZ2SVFSaE9HMHoxQTBRd3hOUmZkak96Z2Y=",
    "jwt_signing_method": "hmac",
    "version_data": {
        "not_versioned": true,
        "versions": {
            "Default": {
                "name": "Default",
                "expires": "3000-01-02 15:04",
                "use_extended_paths": true,
                "extended_paths": {
                    "ignored": [],
                    "white_list": [],
                    "black_list": []
                }
            }
        }
    },
    "proxy": {
        "listen_path": "/bss/",
        "target_url": "http://10.24.101.69:8081/v1/",
        "strip_listen_path": true
    },
    "enable_batch_request_support": true
}

Any output in yue gateway logs? It might be clock skew…

the only output I get is written to syslog (ubuntu 16.04):
Mar 29 08:19:10 ko-tyk tyk[20059]: time=“Mar 29 08:19:10” level=info msg=“Attempted JWT access with non-existent key.” origin=10.24.101.187 path="/bss/user/1"
Mar 29 08:19:10 ko-tyk tyk[20059]: time=“Mar 29 08:19:10” level=error msg=“JWT validation error: illegal base64 data at input byte 0” origin=10.24.101.187 path="/bss/user/1"
Mar 29 08:19:10 ko-tyk tyk[20059]: time=“Mar 29 08:19:10” level=error msg=“request error: Key not authorized” api_id=1 org_id=default path="/bss/user/1" server_name=“http://10.24.101.69:8081/v1/” user_id= user_ip=…

All servers involved have synchronized time (gmt+1).
Even if it was clock skew, shouldn’t it result in “Token expired”?

Looks like there may be an encoding error with the actual key?

I’ve verified these tokens against secret and it validates. Perhaps I misunderstood the docs - for hmac jwt_source should be base64 encoded secret, right?:

“jwt_source”: “d3hJUEY2bGZ2SVFSaE9HMHoxQTBRd3hOUmZkak96Z2Y=”,

I’ve tried raw secret as well but the result is the same.

The validation error is from our JWT lib, so its the base64 decoding of the key that’s the problem.

The JWT is valid, I’ve checked :slight_smile:

How are you making the request? Have you go the header format:

Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwb2xpY3ktaWQiOiJkZWZhdWx0Iiwic3ViIjoxLCJpc3MiOiJodHRwOlwvXC8xMC4yNC4xMDEuNjk6ODA4MVwvdjFcL2F1dGhcL2F1dGhlbnRpY2F0ZSIsImlhdCI6MTQ5MDc2NTU3MSwiZXhwIjoxNDkwNzY5MTcxLCJuYmYiOjE0OTA3NjU1NzEsImp0aSI6IjViNDM3M2VlNjNjN2QzNTBlZjhmZDlhZmZkNTM4MzRlIn0.Q4BlPcK4zWp3tGTZINNKCsepZYq9xqjVgkZGrBWeGzo

The Bearer component is required and might be causing the problem?

Indeed that is my header format.

Can you share your test curl command?

curl -X GET -H “Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwb2xpY3ktaWQiOiJkZWZhdWx0Iiwic3ViIjoxLCJpc3MiOiJodHRwOlwvXC8xMC4yNC4xMDEuNjk6ODA4MVwvdjFcL2F1dGhcL2F1dGhlbnRpY2F0ZSIsImlhdCI6MTQ5MDc3ODg3NSwiZXhwIjoxNDkwNzgyNDc1LCJuYmYiOjE0OTA3Nzg4NzUsImp0aSI6ImFlNjM4ZjJhMWYxMGFjNjUwZDkxNGE0ZjU0ZjBjNmVhIn0.m3BAFy6cJRJIbjkkNw-Sgggch8rJX5RiD3by8s2PyP0” -H “Cache-Control: no-cache” -H “Postman-Token: 9467c305-092e-459c-4f0c-2f4b587f369b” “http://10.24.101.64/bss/user/1

Edit: I think I got it, I’ve had colon after “Bearer”, but now I’ve got following:

“Base Field not found, using SUB”
“ID Could not be generated. Failing Request.”

Ah, there it is - ok, so the next issue is actually just the policy ID: “default-1”, unless you are using CE (file based configuration), this will need to be the numeric ID of the policy that you created in the dashboard.

I’m running CE, but just to be on the safe side, I’ve also just tried numeric policy-id to no avail.

Current policies:

{
        "1": {
                "rate": 1000,
                "per": 1,
                "quota_max": 100,
                "quota_renewal_rate": 60,
                "access_rights": {
                        "41433797848f41a558c1573d3e55a410": {
                                "api_name": "My API",
                                "api_id": "1",
                                "versions": [
                                        "Default"
                                ]
                        }
                },
                "org_id": "default",
                "hmac_enabled": false
        }
}

Can you try adding an id field to the policy object to is having Eb he same as key name (1)?

Didn’t help, sadly. (I do gateway restart every time something is changed)

token:

policy-id: “1”,

policy:

    "1": {
            "id": "1",
            "rate": 1000,
            ...

log:

level=info msg=“Processed and listening on: /bss/{rest:.*}”
level=info msg=“Loading uptime tests…”
level=info msg=“Initialised API Definitions”
level=info msg=“Loading policies”
level=info msg=“Gateway started (v2.3.3)”
level=info msg="–> Listening on address: (open interface)"
level=info msg="–> Listening on port: 80"
level=info msg="–> PID: 20392"
level=info msg=“Starting Poller”
level=warning msg=“Base Field not found, using SUB”
level=error msg=“ID Could not be generated. Failing Request.”
level=error msg=“request error: Key not authorized” api_id=1 org_id=default path="/bss/user/1" server_name=“http://10.24.101.69:8081/v1/” user_id= user_ip=…

By the way - Can api be configured without a policy? If that would allow me to run it properly I could live with that.

This should be listing out the policies that it finds - there should be a bunch of numbers there (well a single one in your case)

It implies the policy was never loaded. Might be worth checking the policy file path on your tyk.conf

You can also try adding the ID as “_id”, though it’s unlikely.

For JWT access, policies are really important since they define how a token may access a set of resources :-/

If I change the policy file path from policies/policies.json to “policies.json” I get
level=error msg=“Couldn’t load policy file: open policies.json: no such file or directory”
so tyk has to be loading that file.

I’ve had some tabs in tyk.conf near policies (I have had assumed it was ok since tyk.conf.example had the same), after removal logs now show api being detected (although it was working without being detected in logs earlier):

Started Tyk API Gateway.
time=“Mar 29 13:28:43” level=info msg=“Connection dropped, connecting…”
time=“Mar 29 13:28:43” level=info msg=“Setting up analytics normaliser”
time=“Mar 29 13:28:43” level=info msg=“PIDFile location set to: ./tyk-gateway.pid”
time=“Mar 29 13:28:43” level=error msg=“Instrumentation is enabled, but no connectionstring set for statsd”
time=“Mar 29 13:28:43” level=info msg=“Initialising Tyk REST API Endpoints”
time=“Mar 29 13:28:43” level=info msg="–> Standard listener (http)"
time=“Mar 29 13:28:43” level=info msg=“Setting up Server”
time=“Mar 29 13:28:43” level=info msg=“Initialising distributed rate limiter”
time=“Mar 29 13:28:43” level=info msg=“Loading API Specification from /opt/tyk-gateway/apps/test-service.json”
time=“Mar 29 13:28:43” level=info msg=“Detected 1 APIs”
time=“Mar 29 13:28:43” level=info msg=“Loading API configurations.”
time=“Mar 29 13:28:43” level=info msg=“Tracking hostname” api_name=“test service” domain="(no host)"
time=“Mar 29 13:28:43” level=info msg=“Starting gateway rate imiter notifications…”
time=“Mar 29 13:28:43” level=info msg=“Loading API” api_name=“test service”
time=“Mar 29 13:28:43” level=info msg=“Checking security policy: JWT” api_name=“test service”
time=“Mar 29 13:28:44” level=info msg=“Processed and listening on: /bss/{rest:.*}”
time=“Mar 29 13:28:44” level=info msg=“Loading uptime tests…”
time=“Mar 29 13:28:44” level=info msg=“Initialised API Definitions”
time=“Mar 29 13:28:44” level=info msg=“Loading policies”
time=“Mar 29 13:28:44” level=info msg=“Gateway started (v2.3.3)”
time=“Mar 29 13:28:44” level=info msg="–> Listening on address: (open interface)"
time=“Mar 29 13:28:44” level=info msg="–> Listening on port: 80"
time=“Mar 29 13:28:44” level=info msg="–> PID: 20955"
time=“Mar 29 13:28:44” level=warning msg=“Base Field not found, using SUB”
time=“Mar 29 13:28:44” level=error msg=“ID Could not be generated. Failing Request.”
time=“Mar 29 13:28:44” level=error msg=“request error: Key not authorized” api_id=1 org_id=default path="/bss/user/1" server_name=“http://10.24.101.69:8081/v1/” user_id= user_ip=…

policies.json:

{
  "1": {
   "rate": 1000,
   "per": 1,
   "quota_max": 100,
   "quota_renewal_rate": 60,
   "access_rights": {
    "41433797848f41a558c1573d3e55a410": {
     "api_name": "My API",
     "api_id": "1",
     "versions": [ "Default" ]
    }
   },
   "org_id": "default",
   "hmac_enabled": false
 }
}

Hi,

So I spent some time replicating this with a fresh CE installation and got it all working :slight_smile:

There’s a couple of things that might have gone wrong, so instead, here’s a working set of files:

test_api_3.json

{
    "name": "JWT-TEST",
    "slug": "jwt-test",
    "api_id": "3",
    "org_id": "test-org",
    "use_keyless": false,
    "use_oauth2": false,
    "use_openid": false,
    "openid_options": {
        "providers": [],
        "segregate_by_client": false
    },
    "oauth_meta": {
        "allowed_access_types": [],
        "allowed_authorize_types": [],
        "auth_login_redirect": ""
    },
    "auth": {
        "use_param": false,
        "param_name": "",
        "use_cookie": false,
        "cookie_name": "",
        "auth_header_name": "Authorization"
    },
    "use_basic_auth": false,
    "enable_jwt": true,
    "use_standard_auth": false,
    "enable_coprocess_auth": false,
    "jwt_signing_method": "hmac",
    "jwt_source": "d3hJUEY2bGZ2SVFSaE9HMHoxQTBRd3hOUmZkak96Z2Y=",
    "jwt_identity_base_field": "sub",
    "jwt_client_base_field": "",
    "jwt_policy_field_name": "policy-id",
    "notifications": {
        "shared_secret": "",
        "oauth_on_keychange_url": ""
    },
    "enable_signature_checking": false,
    "hmac_allowed_clock_skew": -1,
    "base_identity_provided_by": "",
    "definition": {
        "location": "header",
        "key": "x-api-version"
    },
    "version_data": {
        "not_versioned": true,
        "versions": {
            "Default": {
                "name": "Default",
                "expires": "",
                "paths": {
                    "ignored": [],
                    "white_list": [],
                    "black_list": []
                },
                "use_extended_paths": true,
                "extended_paths": {},
                "global_headers": {},
                "global_headers_remove": [],
                "global_size_limit": 0,
                "override_target": ""
            }
        }
    },
    "uptime_tests": {
        "check_list": [],
        "config": {
            "expire_utime_after": 0,
            "service_discovery": {
                "use_discovery_service": false,
                "query_endpoint": "",
                "use_nested_query": false,
                "parent_data_path": "",
                "data_path": "",
                "port_data_path": "",
                "target_path": "",
                "use_target_list": false,
                "cache_timeout": 60,
                "endpoint_returns_list": false
            },
            "recheck_wait": 0
        }
    },
    "proxy": {
        "preserve_host_header": false,
        "listen_path": "/test-3/",
        "target_url": "http://httpbin.org/",
        "strip_listen_path": true,
        "enable_load_balancing": false,
        "target_list": [],
        "check_host_against_uptime_tests": false,
        "service_discovery": {
            "use_discovery_service": false,
            "query_endpoint": "",
            "use_nested_query": false,
            "parent_data_path": "",
            "data_path": "hostname",
            "port_data_path": "port",
            "target_path": "/api-slug",
            "use_target_list": false,
            "cache_timeout": 60,
            "endpoint_returns_list": false
        }
    },
    "disable_rate_limit": false,
    "disable_quota": false,
    "custom_middleware": {
        "pre": [],
        "post": [],
        "post_key_auth": [],
        "auth_check": {
            "name": "",
            "path": "",
            "require_session": false
        },
        "response": [],
        "driver": "",
        "id_extractor": {
            "extract_from": "",
            "extract_with": "",
            "extractor_config": {}
        }
    },
    "custom_middleware_bundle": "",
    "cache_options": {
        "cache_timeout": 60,
        "enable_cache": true,
        "cache_all_safe_requests": false,
        "cache_response_codes": [],
        "enable_upstream_cache_control": false
    },
    "session_lifetime": 0,
    "active": true,
    "auth_provider": {
        "name": "",
        "storage_engine": "",
        "meta": {}
    },
    "session_provider": {
        "name": "",
        "storage_engine": "",
        "meta": null
    },
    "event_handlers": {
        "events": {}
    },
    "enable_batch_request_support": false,
    "enable_ip_whitelisting": false,
    "allowed_ips": [],
    "dont_set_quota_on_create": false,
    "expire_analytics_after": 0,
    "response_processors": [],
    "CORS": {
        "enable": false,
        "allowed_origins": [],
        "allowed_methods": [],
        "allowed_headers": [],
        "exposed_headers": [],
        "allow_credentials": false,
        "max_age": 24,
        "options_passthrough": false,
        "debug": false
    },
    "domain": "",
    "do_not_track": false,
    "tags": [],
    "enable_context_vars": false
}

policies.json

{
	"1": {
		"rate": 1000,
        "id": "1",
		"per": 1,
		"quota_max": 100,
		"quota_renewal_rate": 60,
		"access_rights": {
			"2": {
				"api_name": "TEST-2",
				"api_id": "2",
				"versions": [
					"Default"
				]
			}
		},
		"org_id": "test-org",
		"hmac_enabled": false
	}
}

I was incorrect when I said that the policies should list out, the file loader doesn’t do that, but since I spend 99% of my time debugging dashboard installs I assumed they were the same :-/

A few things to confirm as well:

  • in tyk.conf the policy record name must be policies.json
  • The entry in the policies.json file for access rights should include the ID twice, you have:
"access_rights": {
    "41433797848f41a558c1573d3e55a410": {
     "api_name": "My API",
     "api_id": "1",
     "versions": [ "Default" ]
    }

You need:

"access_rights": {
    "1": {
     "api_name": "My API",
     "api_id": "1",
     "versions": [ "Default" ]
    }
  • the "jwt_identity_base_field": "sub" entry should be set, otherwise Tyk assumes the “SUB” field, and I’m wondering if it’s case sensitive which might have caused side effects.

There might be other irregularities but I can’t spot them.

To generate my JWT token, I used your secret (not base64 encoded) with this handy tool, the JWT claims looked like this:

{
    "iss": "Online JWT Builder",
    "iat": 1490821744,
    "exp": 1522357744,
    "aud": "www.example.com",
    "sub": "[email protected]",
    "GivenName": "Johnny",
    "Surname": "Rocket",
    "Email": "[email protected]",
    "policy-id": "1",
    "Role": "Project Administrator"
}

And the final JWT was:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0OTA4MjE3NDQsImV4cCI6MTUyMjM1Nzc0NCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZTN0QGV4YW1wbGUuY29tIiwicG9saWN5LWlkIjoiMSIsIlJvbGUiOiJQcm9qZWN0IEFkbWluaXN0cmF0b3IifQ.gJ5AfJ24KwpsJrJZbdE-lq9Cz6pEi0L3uIS1PDWFBgo

Let me know if the above templates help.

Cheers,
M.

1 Like

I was about to go give it up, but since your token was working upon closer inspection I’ve spotted the difference - the sub claim in my case is an integer where yours is a string. As JWT RFC states:

The “sub” value is a case-sensitive string containing a StringOrURI value.

I must conclude that jwt validator tyk is using was working correctly all this time and this is a bug in jwt-auth.

Nevertheless, I’ve had my share of errors with policies so thank you very much for your time and great support :smiley:

1 Like