Engineering 17 min read Sep 7, 2026 8

A Valid Signature Is Not Enough: Autonomous Testing for JWT Trust Boundaries

A valid JWT signature does not establish that a token belongs at a particular API. We examine issuer, audience, token profile, and authorization through controlled boundary tests.

JWTSecurity IdentitySecurity OAuthSecurity AutonomousPentesting ApplicationSecurity ZeroTrust DevSecOps
S
SecureHup Team
Security Researcher · SecureHup
A Valid Signature Is Not Enough: Autonomous Testing for JWT Trust Boundaries

An invoice API receives a token with a valid signature from the organization's identity provider. The token was issued for the web client, carries an ID-token profile, and names a different audience. A shared verification helper checks the signature and expiry, returns decoded claims, and the API accepts them. Every cryptographic operation succeeded; the consumer never established that this token was meant to authorize this request.

JWT review should begin with the consumer's acceptance contract, not the token parser. Issuer, audience, token class, algorithm, key source, subject domain, tenant binding, time rules, client binding, and permitted scopes all belong to that contract. Two services using the same signing keys may still need incompatible rules.

Controlled token generation makes these assumptions testable without touching a production identity provider. For each consumer, create tokens that remain cryptographically valid while violating one contextual dimension at a time. The meaningful result is whether any wrong-context token reaches a protected effect, not whether the library raised an error for malformed input.

Key Takeaways

  • Signature validity is necessary for signed JWTs, but it is only one part of token acceptance.
  • Each token consumer needs an explicit contract covering algorithm, key source, issuer, audience, type/profile, time, subject, client, tenant, and authorization claims.
  • Cross-JWT and cross-service confusion occur when a valid token created for one purpose is accepted for another.
  • Key discovery must be bound to a preconfigured trusted issuer; untrusted token headers must not choose arbitrary verification-key locations.
  • Autonomous tests should use synthetic issuers, non-production keys, isolated tenants, and a reviewed acceptance oracle.
  • Every rejected dimension should become a negative regression case triggered when token libraries, identity configuration, gateways, or consuming services change.

What a Signature Actually Proves

A digital signature provides integrity and authenticates the signer relative to a verification key. If verification succeeds, the token was not modified after signing and was signed by a party holding the corresponding key. That statement depends on the application already knowing why it trusts the key.

The signature does not independently prove:

  • That the key belongs to the expected issuer.
  • That the issuer is trusted by this service.
  • That the token was issued for this API.
  • That the token is the expected kind of JWT.
  • That the token is currently valid.
  • That the subject may act in the requested tenant.
  • That the scopes or roles authorize the requested action.
  • That revocation, session state, or membership has not changed.
  • That the token was received through the expected protocol flow.

JWT processing therefore has at least three layers:

  1. Cryptographic validation: structure, allowed algorithm, signature, and key.
  2. Token-context validation: issuer, audience, type/profile, time, client, nonce or replay properties where applicable.
  3. Application authorization: subject, tenant, scope, role, entitlement, object, action, and current state.

Collapsing these layers into verify(token) -> claims creates false confidence. A better interface returns a typed, policy-bound principal only after all required token-context checks succeed. Application authorization still follows.

Define a Token Acceptance Contract

Every consumer should be able to state its acceptance rule without referring to library defaults.

For an API that consumes a profiled JWT access token, the contract may look like:

consumer: billing-api
profile: oauth-access-token
algorithms: [approved-asymmetric-algorithm]
issuer: https://identity.test.example
key_source: pinned-issuer-metadata
audience: urn:service:billing-api
type: at+jwt
required_claims: [iss, sub, aud, exp, iat, client_id, jti]
tenant_binding: membership_service
authorization_source: scopes_and_current_policy
clock_skew_seconds: bounded

This is an illustrative internal representation, not a universal profile. An OpenID Connect client validating ID tokens has different requirements, including flow-specific audience, authorized-party, nonce, and hash checks. A service-to-service assertion follows another specification. The important point is that distinct JWT kinds need mutually exclusive validation rules.

A Formal Acceptance Invariant

For a given consumer C and token J:

accept(C, J) only if:
  algorithm_allowed(C, J)
  AND signature_valid_with_key_owned_by_expected_issuer(C, J)
  AND issuer_exact(C, J)
  AND audience_contains_consumer(C, J)
  AND token_profile_matches(C, J)
  AND temporal_claims_valid(C, J)
  AND subject_and_client_semantics_valid(C, J)
  AND tenant_context_valid(C, J)
  AND requested_action_authorized(C, J)

Some checks happen in middleware and some in business policy. Keeping them in one conceptual contract prevents a valid signature from being mistaken for a complete access decision.

Algorithm Trust Must Be Configured, Not Negotiated by the Token

JWT headers declare an alg value, but the untrusted token cannot decide which algorithms the application is willing to use. The consumer must supply an allowlist appropriate to its issuer and token profile. The library must verify that the declared algorithm matches the actual operation and key type.

Algorithm policy should be narrow:

  • Do not accept none for signed security tokens.
  • Do not allow a broad set merely because the library supports it.
  • Do not mix symmetric and asymmetric verification paths under ambiguous key handling.
  • Bind a key to its intended algorithm and use.
  • Review algorithm changes as trust-policy changes, not routine configuration.

An autonomous code review can locate token verifier construction, inspect whether algorithms are explicit, and identify helpers that decode before validating. Runtime testing then confirms that tokens using non-approved algorithms are rejected. The test uses dedicated test keys and profiles; it does not attempt to derive or misuse production keys.

Issuer and Key Source Form One Trust Decision

Validating an iss string after signature verification is too late if the token already influenced where the verifier obtained its key. The trust direction should run from local configuration to issuer metadata to keys:

configured trusted issuer
  -> approved metadata location
  -> approved JWKS URI
  -> selected key by identifier and algorithm
  -> signature verification
  -> exact issuer claim validation

It should not run from an arbitrary token header to an arbitrary URL and then treat any successful signature as trust.

Header parameters that reference keys or certificates can create both trust confusion and server-side fetch risk when consumed without strict profile rules. For common OpenID Connect ID-token deployments, keys are communicated in advance through discovery and registration rather than selected from untrusted per-token URLs. The applicable token specification and library behavior must govern the implementation.

Key IDs Are Selectors, Not Authorities

A kid helps select among keys already associated with a trusted issuer. It does not identify a globally trusted key. Cache keys should include issuer and other required context, not only kid, because two issuers can legitimately use the same identifier. Unknown identifiers should trigger a bounded refresh of the expected issuer's approved key set, not an open-ended fetch chosen by the token.

Audience Is the Resource Boundary

The audience claim answers who the token is intended for. A token issued for Service A should not be accepted by Service B merely because both trust the same identity provider and use the same signing keys.

Audience failures are common in microservices because teams configure only issuer and signature. A shared gateway may consider the token valid at the edge, then forward it to several backends. If each backend does not enforce its own audience or rely on a gateway contract that cryptographically and operationally preserves that distinction, one service's token can become a credential for another.

Audience validation should:

  • Compare against identifiers explicitly configured for the current resource.
  • Handle string and array representation according to the applicable profile.
  • Reject absence when the profile requires audience.
  • Reject a token whose audiences do not include the current consumer.
  • Avoid substring, suffix, or loose URL matching.
  • Keep scope meaning tied to the addressed resource.

An autonomous test creates a valid token for each synthetic service and presents it to every other consumer in the matrix. Every off-diagonal case should deny unless cross-audience acceptance is an explicit, reviewed design.

Token Type Prevents Cross-JWT Confusion

JWT is a container used by many protocols. Two tokens can share issuer, key, subject, and familiar claims while representing different security objects.

Common categories include:

  • OpenID Connect ID tokens for a client to understand an authentication event.
  • OAuth access tokens for a resource server.
  • Client authentication assertions.
  • Authorization request objects.
  • Logout tokens.
  • Email verification or application session tokens.
  • Service-specific signed commands.

Accepting one category in place of another is cross-JWT confusion. An API must not treat an ID token as an access token simply because the signature verifies. A consumer should enforce explicit type values where its profile defines them and maintain mutually exclusive validation rules even when a type field is absent or optional in another profile.

The OAuth JWT access-token profile, for example, defines explicit at+jwt typing and resource-server validation rules. OpenID Connect ID-token validation is different and flow-sensitive. Generic "verify any JWT from our issuer" middleware erases that distinction.

Time Validation Is More Than Checking exp

Expiration is essential, but time policy also includes not-before, issued-at, maximum age where required, and bounded clock skew. Risks appear when:

  • Expiration is decoded but not enforced.
  • Clock tolerance is so large that expired tokens remain useful.
  • A token with a future not-before time is accepted.
  • Issued-at is trusted for security-sensitive freshness without reasonableness checks.
  • Long-lived tokens preserve stale role or tenant membership.
  • Different services use materially different clock policy.

Not every profile requires every time claim, and iat alone is not a revocation mechanism. The contract should state what is required, what skew is allowed, and when current server-side state must supplement token claims.

Subject, Client, and Tenant Are Different Identities

The sub claim is meaningful only within its issuer and token profile. In a user delegation flow, it may identify a resource owner. In a client-credentials flow, the subject semantics can represent a client. Applications that assume every subject is a human account can grant inappropriate access.

The client that obtained a token is also not automatically the user represented by it. Where the profile supplies client_id, authorized-party, or equivalent context, consumers should apply the correct semantics instead of collapsing all identity into one string.

Multi-tenancy adds another boundary. A signed tenant_id claim states what the issuer asserted at issuance time, but the resource server still needs policy for:

  • Whether the issuer is authoritative for that tenant.
  • Whether the subject currently belongs to the tenant.
  • Whether the token audience refers to the tenant-aware resource.
  • Whether a client may choose or influence the tenant claim.
  • Whether membership or role changes require current-state lookup.
  • Whether tenant switching requires a new token or explicit context binding.

A token should not become a portable bag of claims that every service interprets differently. Claim semantics are part of the contract.

Authorization Claims Still Need Application Context

Scopes, roles, groups, and entitlements in a valid token are inputs to authorization, not universal permission. The resource server must know what each claim means for its own operations.

A scope named read is meaningless without a resource and object model. A role named admin may be tenant-specific, application-specific, or identity-provider administrative state. A token can be valid for the right audience and still lack permission for the requested action or object.

Good design combines token claims with current policy:

token accepted for this consumer
AND scope permits action category
AND subject has current tenant membership
AND object belongs to authorized boundary
AND resource state permits action

Autonomous JWT testing should therefore connect token mutation to protected effects. A 200 from a health endpoint does not prove privilege escalation. The test must identify an action whose policy depends on the changed claim and verify whether the effect occurred.

Code-Aware Consumer Inventory

The first job of an autonomous test system is finding every place that accepts JWT-derived authority.

Sources include:

  • API gateways and ingress middleware.
  • Backend services and GraphQL resolvers.
  • WebSocket connection and message authorization.
  • Background jobs carrying serialized identity context.
  • Service-to-service calls.
  • File download and signed-action endpoints.
  • Mobile and web backends.
  • Admin tools and internal APIs.
  • Agent and tool servers that consume OAuth tokens.

For each consumer, collect:

  • Library and version.
  • Verifier construction and algorithm configuration.
  • Trusted issuer and discovery source.
  • JWKS caching and refresh behavior.
  • Expected audience and token kind.
  • Required claims and time policy.
  • Subject, client, and tenant interpretation.
  • Scope-to-action mapping.
  • Whether upstream validation is trusted and how that trust is protected.
  • Failure behavior and telemetry.

The output is a token-consumer graph. Shared helpers become high-value nodes because one permissive default can affect several services.

Safe Autonomous Test Environment

JWT boundary testing should never require signing tokens with production keys. Build a dedicated identity lab.

Synthetic Trust Topology

Provision:

  • Trusted test issuer A with keys registered in the test consumer.
  • Untrusted test issuer B with independent keys.
  • Two test resource servers with distinct audiences.
  • A test OpenID client expecting ID tokens.
  • Synthetic users and tenants with no production identity relationship.
  • Controlled key rotation and cache endpoints.

The lab can create tokens that are cryptographically valid for their own issuer and profile. The test then asks whether the consumer enforces context, not whether production cryptography can be broken.

No Secret Leakage

Store private test keys in the isolated test environment, rotate them after runs when appropriate, and never include raw tokens in reports. Evidence uses token fingerprints and decoded non-sensitive fixture claims.

Policy Oracle

Each consumer's reviewed contract determines expected acceptance. If issuer or audience intent cannot be established, the agent reports policy ambiguity rather than declaring a bypass.

Mutation Matrix

Start from a valid control token for each consumer and change one trust dimension at a time.

Test Cryptographic status Context change Expected result
Valid control Valid None Accept token, then apply action policy
Disallowed algorithm Otherwise valid in lab Algorithm outside consumer allowlist Reject
Wrong issuer Valid for issuer B iss and key belong to untrusted issuer Reject
Issuer/key mismatch Signature valid under unrelated configured context Claim does not match key owner Reject
Wrong audience Valid for issuer A Audience is Service B Service A rejects
Missing required audience Valid Required claim absent Reject
ID token at API Valid ID token Wrong token class Reject
Access token at client ID-token handler Valid access token Wrong token class Reject
Expired token Valid signature exp elapsed beyond bounded skew Reject
Not-yet-valid token Valid signature Future activation Reject where applicable
Unknown key ID Valid lab scenario Key not in approved issuer set Bounded refresh, then reject if still unknown
Key-ID collision Valid tokens from two issuers Same kid, different issuer Cache remains issuer-scoped; untrusted token rejected
Wrong tenant Valid for audience Subject lacks current target-tenant membership Reject protected action
Excess scope or role Valid token Claim not authorized by issuer/client policy Reject or ignore unauthorized claim
Rotation overlap Valid old and new approved keys Within and outside documented overlap Accept only inside policy window

Additional flow-specific cases should be added for nonce, authorized party, authentication time, sender constraints, or replay identifiers when the applicable protocol requires them.

Testing JWKS Caching and Rotation

Key rotation is where correct validation can become unreliable or fail open.

A secure cache design should:

  • Namespace keys by trusted issuer and identifier.
  • Honor approved metadata rather than per-token arbitrary locations.
  • Refresh unknown keys with rate limits and request coalescing.
  • Avoid unbounded outbound fetches triggered by attacker-selected identifiers.
  • Preserve known good keys for a documented overlap where required.
  • Reject tokens when key ownership cannot be established.
  • Distinguish temporary metadata failure from permission to skip verification.
  • Emit telemetry for refresh, unknown key, and validation failure.

Autonomous tests rotate test issuer keys while traffic runs. They verify acceptance of the new approved key, bounded behavior for the retired key, isolation between issuers with colliding key IDs, and fail-closed behavior when discovery is unavailable. The goal is both security and operational correctness; a rotation process that causes widespread false rejection will be bypassed under pressure.

Evidence for a Verified JWT Trust Failure

A finding should contain:

  • Consumer, route, and protected action.
  • Reviewed token acceptance contract.
  • Token profile and safe fingerprint.
  • Relevant synthetic header and claims, excluding secrets and real identities.
  • Which checks passed and which trust dimension should have failed.
  • Request and observed response.
  • Protected effect or data access, when authorization impact is claimed.
  • Verifier configuration and likely root cause.
  • Scope across sibling consumers sharing the helper.
  • Corrected behavior and deterministic replay.

Do not call every rejected-token inconsistency an authentication bypass. A consumer that returns the wrong error message has a different issue from one that performs a protected action with a wrong-audience token. Severity follows reachable impact.

Repair the JWT Trust Binding

  1. Inventory every JWT consumer and assign an owner.
  2. Define a separate acceptance contract for each token profile and consumer.
  3. Configure a narrow algorithm allowlist.
  4. Pin trusted issuers and derive keys only through approved metadata or configuration.
  5. Validate exact issuer and consumer-specific audience.
  6. Enforce token type/profile and mutually exclusive validation rules.
  7. Validate required temporal and flow-specific claims with bounded skew.
  8. Bind subject, client, and tenant semantics to current application policy.
  9. Treat scopes and roles as authorization inputs, not complete decisions.
  10. Namespace JWKS caches by issuer and constrain refresh behavior.
  11. Reject on validation ambiguity; do not return decoded claims as a principal.
  12. Add negative tests and exploit replay for every trust dimension.

When a maintained library has a security update, upgrade it, but do not assume the upgrade supplies application-specific issuer, audience, or tenant policy. Libraries implement mechanisms. The service owns trust configuration.

CI/CD and Continuous Validation

Trigger focused JWT tests when a pull request changes:

  • Authentication or authorization middleware.
  • Token libraries or verifier wrappers.
  • Identity-provider discovery or issuer configuration.
  • Service audience identifiers.
  • Gateway claim forwarding.
  • JWKS cache or network behavior.
  • Token issuance profiles and claims.
  • Tenant, role, scope, or membership logic.
  • API routes that begin trusting token-derived fields.
  • Infrastructure secrets or keys used for verification.

Fast tests can validate configuration and mutation cases with local test issuers. Integration tests should cross actual gateway and service boundaries. Scheduled rotation tests can exercise cache refresh and outages. Block a merge when a deterministic test proves that a token outside the reviewed contract reaches a protected effect.

Record rejected dimensions as coverage, but avoid saying the service is universally secure because a finite matrix passed. New token consumers, profiles, and trust relationships change the model and require new cases.

FAQ

If the JWT signature is valid, can we trust its claims?

Only after the application establishes that the key belongs to an expected issuer and validates the token's audience, type, time, and applicable protocol rules. Claims then become authenticated assertions from that issuer, but application authorization still decides what they permit.

Is checking issuer and expiration enough?

No. The consumer also needs algorithm and key-source policy, audience, token profile, and application-specific subject, tenant, scope, and action checks. Flow-specific tokens may require additional validation.

Can an ID token be used as an API access token?

No by default. An ID token is intended for the OpenID Connect client and follows ID-token validation rules. A resource server should require the access-token profile it supports and reject the wrong token class even when issuer and signature are familiar.

Should the token's jku or similar header choose the verification key URL?

Not as a generic trust mechanism. Key retrieval must follow the applicable profile and a locally configured trust relationship. For common OpenID Connect deployments, key locations are established through approved discovery and registration, not arbitrary per-token URLs.

How can autonomous testing create wrong-issuer tokens safely?

Use an isolated test issuer with its own non-production keys and synthetic identities. The token is valid for that test issuer, and the test verifies that a consumer configured for another issuer rejects it. No production key forging is involved.

Every Consumer Has Its Own Token Contract

Services that accept tokens may share keys while requiring different issuers, profiles, audiences, subject domains, algorithms, tenant rules, and privileges. An explicit acceptance contract for each consumer turns those differences into testable context. Cryptographically valid tokens with one wrong dimension are particularly useful because they isolate trust decisions from parser and signature failures.

If a wrong-context token reaches a protected effect, we would retain it as a safe regression. It also sharpens review language: instead of saying “the token is verified,” the team can state which issuer, profile, audience, subject domain, and privilege this particular consumer accepted. Cryptography authenticates the bytes; the consumer still decides whether they belong here.

#JWTSecurity #IdentitySecurity #OAuthSecurity #AutonomousPentesting #ApplicationSecurity #ZeroTrust #DevSecOps

Was this helpful?
Share :

Find issues like this in your own codebase

SecureHup runs this exact exploit chain — discovery, enumeration and cross-tenant validation — on every deployment, automatically.