VAPT Fundamentals

How to Secure JWTs: Validation, Key Management, and Session Protection

image

JWT security depends on the decisions surrounding the token, who can issue it, which application can accept it, how long it remains useful, and what happens when access must end. A maintained JWT library handles the cryptographic operations. Your application still needs an explicit trust policy, sensible session behavior, and protection against credential exposure. This blog focuses on securing JWT implementations. Hands-on assessment procedures are covered separately in JWT Penetration Testing: A Practical Guide to Finding and Validating Vulnerabilities.

Understand what a signed JWT provides

A JSON Web Token contains claims about a subject and its intended use. A common signed JWT has three parts: an encoded header, an encoded payload, and a signature. The signature protects integrity; the readable payload is not made confidential by Base64url encoding. JWTs can also use encryption, but signing and encryption serve different purposes. JWT specification, JSON Web Signature specification

For example, an API might receive a token identifying a user and granting a reports:read scope. The API must still decide whether that user may read the particular report being requested.

Keep sensitive information out of tokens unless there is a justified requirement and suitable protection. Do not include passwords, signing secrets, or unrelated personal data merely because the payload can hold them.

1. Define the token’s purpose before configuring validation

Write down a small policy for every token type the application accepts. The following is an illustrative policy for one reporting API, not universal configuration syntax:

image

Different token purposes need distinct validation rules. An ID token and an API access token are not interchangeable simply because the same platform issued both. The value of this policy is clarity: developers and reviewers can compare implementation behavior against a concrete expectation.

2. Enforce algorithm and key restrictions

    Configure the verifier with the algorithms the application accepts. Bind each verification key to its intended algorithm, and reject incompatible combinations. For an application expecting signed authentication tokens, do not enable unsigned-token acceptance. This prevents the token’s own header from freely selecting the verification policy. It also addresses algorithm-confusion failures, where a verifier incorrectly uses asymmetric public-key material as an HMAC secret.

    Use the library’s verification interface for security decisions. A decode function is useful for inspection, but reading claims does not establish their authenticity. Keep JWT and cryptographic dependencies maintained, and review security-relevant configuration when upgrading.

    3. Protect signing material and separate signing from verification

      With HS256, the signer and verifier share a secret. Any service holding that secret has the cryptographic ability to generate tokens. RFC 7518 requires an HS256 key of at least 256 bits; generate key material using a cryptographically secure source rather than a memorable string. Character count alone does not establish randomness.

      Asymmetric signing allows an issuer to retain its private key while distributing public verification keys. This can suit architectures in which several APIs should verify tokens without gaining signing capability. It does not remove the need to protect the issuer or configure verification correctly. Keep signing material in an appropriately protected secret store or signing service, restrict access to the components that need it, and keep it out of source code and logs. Document ownership so that rotation and incident response are not dependent on one person’s knowledge.

      4. Resolve keys only within established trust

        A kid value is a key identifier, not proof of trust. Use it to select from the expected issuer’s configured keys. Do not turn an arbitrary identifier into an unrestricted file path or database query. Similarly, do not trust a sender-supplied key merely because its signature verifies. Constrain remote key locations rather than blindly following jku or x5u headers; unrestricted retrieval can also introduce server-side request forgery.

        For a multi-issuer deployment, maintain an explicit mapping between accepted issuers and their trusted configuration. A token may help select an already trusted configuration; it must not create a new trust relationship just by naming a URL.

        5. Require and validate the claims your application needs

          Claim validation has two parts: confirming that required values exist and confirming that their values are acceptable.

          image

          The base JWT standard defines these claims without requiring every one in every JWT. Follow the selected profile and your application’s requirements. A unique token identifier does not, by itself, prevent replay. For the OAuth JWT access-token profile, issuer and audience checks are mandatory parts of validation. A valid signature from a shared identity platform is insufficient if the token was meant for a different API.

          Use a small, documented clock tolerance where needed, and include it when reasoning about effective token lifetime. Avoid letting a generous tolerance undermine a deliberately short validity window.

          6. Keep resource authorization explicit

            After token validation, enforce permission for the requested action and resource. Check ownership, tenant membership, and relevant business rules on the server. A reports read scope may permit the category of operation without granting access to every report.

            For example, a reporting API can establish the user’s identity, resolve the requested report, and verify that the user belongs to the report’s tenant and has access to that report. A tenant identifier supplied elsewhere in the request must not silently override this decision.

            7. Design expiry, refresh, and invalidation together

              Short access-token lifetimes reduce the period during which a leaked token remains useful. The appropriate duration depends on privilege, application risk, and the ability to refresh sessions. Do not present one lifetime as universally secure.

              If refresh tokens are issued, their protection matters just as much. OAuth public clients must use sender-constrained refresh tokens or rotation to detect replay. Rotation replaces the refresh token and retains enough relationship information to detect reuse; sender constraint binds use to a particular client instance. Refresh authorization must remain restricted to the granted scope and resources.

              Define what logout means for both credentials. Clearing the browser’s copy is a local action; the server-side design determines whether a retained credential remains usable. Decide how account disablement and privilege removal affect existing access. Possible application designs include short validity windows, a server-side session or revocation check, or fresh authorization checks for sensitive actions. Choose the mechanism that meets the required invalidation delay, and account for its availability and operational cost. Make these decisions explicit before calling the session design complete.

              8. Choose browser storage with its trade-offs in mind

                JavaScript-accessible storage can expose tokens when malicious script runs in the application’s origin. An HttpOnly cookie prevents direct JavaScript access to that cookie, but does not prevent an injected script from making authenticated requests. Cookies also require suitable CSRF defenses and appropriate Secure, SameSite, domain, and path settings for the application.

                Choose storage around the application’s architecture and threat model. Do not assume that moving a token into a cookie resolves every session risk. Map the systems that can receive credentials, including gateways, tracing platforms, analytics, support exports, and error reporting. Minimize copies and avoid placing reusable bearer tokens in URLs or diagnostic output.

                9. Plan routine rotation and emergency invalidation separately

                  For routine rotation, a bounded overlap can allow existing tokens to expire while new tokens use the new key. Publish and distribute verification material in an order that avoids unnecessary outages, then retire the old key according to the documented window.

                  A compromised signing key changes the decision. Continuing to trust it can permit newly forged tokens, so a routine grace period may be inappropriate. The response plan should cover retiring trust, replacing signing material, and addressing affected sessions. Include verifier caches in that plan. Record how services refresh keys and how emergency changes reach them. An issuer-side change is only one part of invalidation across a distributed application.

                  10. Log validation failures without logging credentials

                    Log enough information to investigate failures without storing raw bearer tokens. Useful fields include the failure category, request identifier, route, and timestamp. Bound and sanitize values derived from untrusted input. OWASP specifically identifies access tokens as data that should generally be excluded from logs.

                    An illustrative event:
                    {
                    "event": "token_validation_failed",
                    "reason": "audience_mismatch",
                    "request_id": "req-example-042",
                    "route": "/api/reports"
                    }

                    Investigate patterns alongside deployment and identity-provider events. Signature failures can indicate tampering, but can also result from stale clients or key-distribution problems. Keep sensitive diagnostic details out of client-facing errors.

                    Securing JWTs starts with an explicit answer to what the application trusts: a particular issuer, a controlled set of keys and algorithms, an intended token purpose, and a bounded validity period. Build authorization and session invalidation around those checks. Protect signing material, control where credentials are stored, and make rotation and incident handling operationally realistic. Together, these decisions make a token useful only in the context where it was intended to work and give the application a clear way to withdraw that trust when circumstances change.