Bearer [redacted] "invalid_token", error_description="the signature key was not found"
A debugging skill for the invalid_token error 'the signature key was not found' on API calls. It explains what a missing signature key means (key rotation, stale JWKS cache, wrong issuer or tenant), how to compare the token's kid header against the JWKS endpoint, and how to re-issue a working token. Use when an API rejects a Bearer token with invalid_token. Triggers: 'signature key was not found', 'invalid_token'. Not for: expired tokens, malformed JWTs, or OAuth code-exchange failures.
Bearer [redacted] "invalidtoken", errordescription="the signature key was not found"
TL;DR
The API cannot find the public key that signed your token, so it cannot verify the signature and rejects the call. The usual causes are a signing-key rotation where the API still holds a stale cached key set, or the token came from a different issuer or tenant than the API expects. Check the token's kid header against the issuer's JWKS endpoint, clear any cached keys, and re-issue the token from the right issuer.
Bearer [redacted] "invalid_token", error_description="the signature key was not found"Note: your own logs may show the redacted placeholder in angle brackets where the token was. The error text after it is what matters.
Use this when
- An API call returns invalid_token with "the signature key was not found"
- A working integration breaks right after the identity provider rotated keys
- Tokens minted in staging fail against production, or vice versa
- A multi-tenant setup starts rejecting tokens for one tenant only
Not for this skill when
- The error is about expiry ("token has been expired or revoked")
- The token itself is malformed or fails to decode
- The failure happens during the OAuth code exchange rather than on API calls
Steps
- Decode the token header offline. You only need the header, never the signature, and you do not need any library:
python3 -c "
import base64, json
h = '[paste the token]'.split('.')[0]
h += '=' * (-len(h) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(h)), indent=2))
" Expected: JSON showing alg (usually RS256), and a kid value identifying the signing key.
- Fetch the issuer's current key set and compare. The JWKS endpoint is published at the issuer's well-known URL:
curl -s https://example.com/.well-known/jwks.json | python3 -c "import json,sys; print([k['kid'] for k in json.load(sys.stdin)['keys']])" Expected: the token's kid appears in the list. If it does not, the keys rotated or the token came from a different issuer.
- If the kid is missing, flush the API's cached key set. Many gateways and SDKs cache JWKS for minutes to hours. Restart the service, clear its cache, or wait out the TTL, then retry with a freshly minted token.
Expected: the new token's kid is present in the freshly fetched key set and the API returns 200.
- Rule out a wrong-issuer token. Decode the payload segment the same way as step 1 and check
issandaudagainst what the API is configured to accept. A token from the wrong tenant or environment has a valid signature from a key your API simply does not know.
Expected: iss matches the API's configured issuer exactly. If not, point the client at the right issuer and re-authenticate.
Variant: "the signature key was not found" after key rotation
Identity providers rotate signing keys on a schedule. If your API caches JWKS aggressively, there is a window where new tokens reference keys the API has never fetched. Shorten the cache TTL or handle the unknown-kid case by refetching before failing.
Variant: kid mismatch in Auth0, Okta, or Keycloak
Same diagnosis, provider-specific JWKS URL. Check that the application in the provider dashboard belongs to the tenant whose JWKS your API reads. Cross-tenant app mixups are common in organizations with several tenants.
Variant: invalid_token with a valid kid
Then the problem is not the key lookup. Move on to signature verification (was the token tampered with?), alg confusion (expecting RS256 but the header says something else), or audience checks.
Variant: works with a new token, fails with an old one
Old tokens signed by a retired key keep failing forever. That is by design. Re-issue rather than trying to resurrect the old key.
Why this happens
RS256-signed tokens are verified with the matching public key, which the API looks up by the kid in the token header from the issuer's published key set. Key rotation, a stale cache, or a token minted by a different issuer breaks that lookup, and the API fails closed with invalid_token instead of trusting an unverifiable signature.
Edge cases and pitfalls
- Some providers publish several keys during a rotation overlap. Your API must try the right one by
kid, not just grab the first. - A long JWKS cache TTL turns every rotation into an outage. Keep it short or make unknown-kid trigger a refetch.
- Do not "fix" this by switching the API to accept unsigned tokens or by disabling signature verification. That trades a lookup failure for no authentication at all.
- Logging full tokens to debug this leaks credentials into your logs. Log the
kid,iss, andalgonly.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_CPov164tvD0maPtHIWk03Q
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.