Open Source API Gateway

401 Unauthorized: JWT Validation Failed

Membrane API Gateway returns 401 Unauthorized with a link to this page when the jwtAuth plugin finds a well-formed JWT and a key for its kid, but the token fails verification. The request is rejected in the request flow.

The Error Response

The response contains the status 401 Unauthorized, a detail that names the failed check, the problem type https://membrane-api.io/problems/security and a link to this page. The type security means that a security plugin refused access to the API. The same check is also logged at info level.

What the Plugin Checks

CheckConfiguration
Signature with the key selected by kidjwks
exp present and not expired (30 seconds clock skew)Always
sub presentAlways
audexpectedAud
issexpectedIss
tidexpectedTid

Common Causes

JWT Signature Is Invalid

The signature does not verify with the key the kid selects. The token was altered, or it was signed with a different key that uses the same kid.

The Token Has Expired

The exp claim lies in the past, with 30 seconds of allowed clock skew. Also check that the clocks of the gateway and the identity provider are in sync.

A Required Claim Is Missing

The token has no exp or no sub claim. Both are required.

The Audience Does Not Match

The aud claim does not match expectedAud. A token with an aud claim also fails when expectedAud is not set at all.

The Issuer or Tenant Does Not Match

The iss claim does not match expectedIss, or the tid claim does not match expectedTid.

How to Fix It

  1. Client: get a fresh token from the identity provider and send it before it expires.
  2. Decode the token and compare its aud, iss and tid claims with the configuration.
  3. Operator: set expectedAud, expectedIss and expectedTid to the values the provider puts into its tokens:
api:
port: 4000
flow:
- jwtAuth:
expectedAud: order
expectedIss: https://auth.example.com
jwks:
jwksUris: https://auth.example.com/keys
target:
url: http://localhost:3000
api:
  port: 4000
  flow:
    - jwtAuth:
        expectedAud: order
        expectedIss: https://auth.example.com
        jwks:
          jwksUris: https://auth.example.com/keys
  target:
    url: http://localhost:3000

To accept any audience, set expectedAud: any!!. This is strongly discouraged. See the JWT tutorial for more.

Hide Details in Production

By default, Membrane runs in development mode and adds details to error responses that help you find the problem. These details may reveal information about your setup. In production, turn them off:

configuration:
production: true
configuration:
  production: true

Add the configuration document to your apis.yaml and separate it from the APIs with ---.

Related Documentation

Configuration reference:

Tutorials with runnable examples:

← All Membrane error messages

Still stuck? See the troubleshooting guide or ask on GitHub Issues. For commercial support, contact info@predic8.de.