JWTs, or JSON Web Tokens, are a popular and widely supported way to represent identity and access claims in modern applications. Whether you're building an API-intensive mobile app, a Single-Page Application (SPA), or an API-driven backend, JWTs can help enable stateless and secure communication between services.
But while JWTs are easy to generate and verify, how you use them makes all the difference. Poor decisions around storage, expiration, or validation can leave your application vulnerable.
This article outlines some of the most essential best practices for using JWTs securely and effectively in both web and mobile contexts.
Store Tokens Securely
One of the most common questions developers ask is, "Where are JWT tokens stored?" Where to store JWTs depends on whether you're building for the web or native, and what kind of tokens you're storing.
For web applications, the preferred storage location is a Secure
& HttpOnly
cookie. This approach, along with a couple of additional settings, minimizes exposure to Cross-Site Scripting (XSS) attacks by ensuring that JavaScript running in the browser can't access the token. It's also naturally compatible with existing cookie-based flows like Cross-Site Request Forgery (CSRF) protection.
In contrast, storing JWTs in localStorage or sessionStorage is discouraged for sensitive use cases, as these storage mechanisms are accessible via JavaScript and more vulnerable to injection attacks.
One way to reduce token exposure is by using the Backend for Frontend (BFF) architecture offered by Duende Software. With this pattern, all authentication and token handling occurs server-side, including issuing Secure
& HttpOnly
cookies to maintain the session. This prevents JWTs from ever being exposed to browser storage or JavaScript access.
For native apps, use platform-secure storage APIs. For example, in iOS, use Keychain, and in Android, use Keystore.
Keychain and Keystore are designed to isolate sensitive information and provide encryption at rest, making them ideal for storing tokens used to allow system access, as well as refresh tokens.
Keep in mind: While secure storage of JWTs used to allow system access is essential, refresh tokens deserve extra care. Refresh tokens are typically longer-lived and can be used to obtain new access tokens without requiring the user to log in again. This makes refresh tokens more powerful and more sensitive! If a refresh token is compromised, an attacker could potentially maintain long-term system access without triggering reauthentication.
Again, consider using Duende's BFF framework for secure token management.
Always Use HTTPS
In most common implementations, JWTs are used as bearer tokens, meaning that whoever possesses the token can use it to access protected resources. That makes transport security non-negotiable. Without HTTPS, an attacker could intercept a token via a man-in-the-middle attack and use it to impersonate a user, or even use the token contents for nefarious purposes if it contains confidential information. Some specialized scenarios (for example, sender-constrained tokens using mTLS or DPoP) provide stronger security, but are outside the scope of this article.
Whether you're building a production app, testing a prototype, or deploying to internal environments, be sure to enforce HTTPS by default. Modern browsers, frameworks, and identity platforms all assume it's in place. Your security depends on it.
Keep JWTs Short-Lived
Short expiration times help limit the damage if a token is ever leaked or stolen. If a JWT is valid for a mere 10 minutes, an attacker only has a narrow window of opportunity to use that token to access the system or impersonate the user.
In web and mobile scenarios, it's common to use JWTs that expire quickly, let's say within 5–15 minutes, and longer-lasting refresh tokens that allow the user to get a new JWT without logging in again.
JWTs support this pattern using the built-in expiration (exp
) claim, and platforms such as Duende IdentityServer make it easy to configure expiration policies and token lifetimes.
Avoid Overloading the Payload
JWTs may be relatively small, but they are transmitted with every authenticated request. Packing too much data into them, such as verbose user metadata, can cause a performance hit. This is especially true on mobile or low-bandwidth networks.
Best practices include:
- Keep claims minimal: common claims include a user ID and expiration timestamp, though other claims may be helpful or even required depending on the purpose of the JWT
- Use identifiers instead of verbose objects: for example, "1234" instead of "John Q User"
- Consider using standardized claim names when working with established identity systems
Smaller tokens are easier to transmit, faster to validate, and less risky if exposed.
Validate JWTs on Every Request
Remember that JWTs are stateless. Therefore, the server must validate them every time they're used. At a minimum, this should include checking the following:
- The token's signature: to ensure it hasn't been tampered with, by recomputing the signature using your known key, then confirming it matches the one in the JWT
- The
exp
claim: to ensure it hasn't expired - The issuer (
iss
) and audience (aud
) claims: to make sure the token is intended for your app
Tools and libraries like .NET's Microsoft.IdentityModel.Tokens and System.IdentityModel.Tokens.Jwt, or authentication platforms like Duende IdentityServer, provide built-in methods for validating JWTs without requiring you to reimplement low-level cryptographic logic.
Know When JWTs Are the Right Tool
JWTs are incredibly useful, but they're not always the right choice.
Use JWTs when:
- You're building APIs that serve multiple clients
- You need to support stateless authentication
- You're working with distributed systems, SPAs, or native apps that interact with APIs
- You want compatibility with OAuth 2.0 and OpenID Connect
Avoid JWTs when:
- You have a simple web app with server-side rendering and session state
- You need easy server-side revocation
- Your app doesn't need to communicate across services or devices
In those cases, a traditional cookie-based session might be simpler and more secure.
If you need easy server-side revocation, self-contained JWTs may not be the right tool, since once issued, they can't be revoked without additional infrastructure. In these cases, consider using reference tokens, which are stored server-side and can be revoked at any time. Platforms such as Duende IdentityServer support both approaches and give you the flexibility to choose the right token type for your application.
Use Strong Signing Algorithms
Always specify a signing algorithm for your JWTs. Never set the algorithm to "none," which would look like this as a JSON snippet: "alg": "none"
.
Instead, it's recommended that you favor asymmetric signing strategies such as RS256, ES256, or PS256, especially in distributed systems where the signer and verifier are different parties.
Asymmetric signing provides non-repudiation since only the private key holder can issue a valid token, and any verifier with the public key can confirm its origin.
Should You Encrypt JWTs?
By default, JWTs are signed but not encrypted. This means that anyone who intercepts one can read the claims, even if they can't modify them.
In many cases, that lack of encryption is acceptable. As long as you're using HTTPS and avoiding sensitive personal data in the payload, signing is enough. Encryption adds overhead and complexity, and is not required for most tokens used to allow system access.
However, if your tokens carry confidential data such as medical details, internal business logic, or PII, then use JSON Web Encryption (JWE) to encrypt them. Just be aware that this complicates implementation and key management.
Signing protects against tampering. Encryption protects against loss of confidentiality. Use both only when your use case truly calls for it.
Final Thoughts
JWTs offer a powerful and flexible way to represent identity and authorization data across web and mobile apps. Their effectiveness depends on several factors, including how they're used, where they're stored, and how they're validated.
By following these best practices, you can avoid common pitfalls and make your JWT-based architecture more secure, scalable, and maintainable. For production-ready tooling and deeper guidance, Duende Software's documentation library is a valuable place to continue your journey.