JWT (JSON Web Token) is a compact format for transferring data between parties as a JSON object protected by a digital signature. The token can be signed using HMAC (symmetric key) or RSA/ECDSA (asymmetric pair), which ensures data integrity and authenticity. According to IETF RFC 7519, 2015, JWT is used in millions of applications for authentication, secure claims exchange, and as the ID Token format in OpenID Connect.
Key Takeaways
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way to transmit information between parties as a JSON object. The information in JWT is called claims — statements about the subject (user) and additional attributes. Each claim is a key-value pair: user identifier, role, expiration time, issuer.
JWT is called self-contained because all the information needed for verification is inside the token itself. The server does not need to access a database or external storage to verify the token's validity — it only needs to check the signature. This property makes JWT ideal for distributed systems and microservice architectures, where multiple services must authenticate requests without a shared session store.
According to Auth0, 2025, more than 65% of mobile and web applications use JWT as the primary token format for API authentication, outpacing opaque tokens and session identifiers.
JWT consists of three parts separated by dots: header.payload.signature. Each part is a Base64url-encoded JSON. Let us examine each part in detail.
Header contains two mandatory fields: alg (algorithm — signing algorithm) and typ (type — token type, always “JWT”). The algorithm can be symmetric (HS256 — HMAC with SHA-256) or asymmetric (RS256 — RSA with SHA-256, ES256 — ECDSA with P-256). Asymmetric algorithms are preferred because they allow the client to verify the signature without possessing the secret key.
Example of a decoded header:
{
"alg": "RS256",
"typ": "JWT",
"kid": "key-id-1"
}
Payload contains claims — statements about the subject. Claims are divided into three types: registered (iss, sub, aud, exp, nbf, iat, jti), public (defined by the developer in the IANA Registry), and private (agreed upon between parties). sub (subject) is the unique user identifier. exp (expiration) is the token expiration timestamp. iss (issuer) is the token issuer.
{
"sub": "user-abc-123",
"iss": "https://auth.example.com",
"aud": "my-mobile-app",
"exp": 1812345678,
"iat": 1812342078,
"role": "premium_user"
}
Signature is created by applying the signing algorithm to the concatenation of the header and payload using a secret or private key. The formula: HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret) for HMAC, or RSASHA256(...) for asymmetric algorithm. The recipient computes the signature the same way and compares it with the received one — if they match, the data has not been altered.
The workflow with JWT consists of two phases: token creation (issuance) by the authentication server and token verification by the client or resource server. The authentication server receives the user’s credentials, creates a payload with claims, and signs it. The resulting JWT is sent to the client in response to a login request or in the OAuth 2.0 / OpenID Connect response body.
In mobile applications, JWT is used as follows: after successful login, the user receives an access token in JWT format. The application stores it in a secure storage (Keychain on iOS, EncryptedSharedPreferences on Android). With each API request, the application adds the Authorization: Bearer <token> header. The API server verifies the JWT signature, extracts claims, and makes access decisions based on them — without querying the database.
According to Google Codelabs, 2025, using JWT in Firebase Authentication reduces the number of requests to the authentication server by 40–60% compared to session tokens, since data is verified locally on each microservice. This is especially important for high-load architectures where every millisecond of latency impacts user experience. At 50,000 requests per minute, switching to JWT can save up to 10 server instances handling introspection requests.
JWT and Session Token solve the same problem — request authentication — but differ fundamentally in architecture. Session Token is a random identifier string that references session data stored on the server (stateful). JWT is a self-contained token containing all data within itself (stateless).
| Parameter | JWT | Session Token |
|---|---|---|
| Data storage | Inside the token (self-contained) | On the server (session storage) |
| Scaling | No shared storage required | Requires Redis/DB for multi-server |
| Token revocation | Complex (needs blacklist) | Simple (delete session from DB) |
| Size | Large (500–2000 bytes) | Small (16–64 bytes) |
| Signature verification | Cryptographic | None (string comparison) |
JWT wins in distributed systems: microservices can verify the token locally without a shared store. For example, in an architecture with five microservices, each service verifies JWT in 1–2 ms without a network call, while a session token requires a centralized Redis lookup on every request, adding 10–30 ms of latency. However, JWT is difficult to revoke — once a token is issued, it is valid until it expires. Session Token is easy to revoke by deleting the record from the DB or Redis.
For mobile applications, a combined approach — JWT with a short lifetime (15–30 minutes) and Refresh Token — provides a balance between performance and security. JWT is used for API access, while the refresh token (usually opaque) is used to obtain new JWTs. If a JWT is compromised, the attacker has access for 15–30 minutes; if a refresh token is compromised, the session is blocked through rotation and reuse detection.
Security of JWT depends on proper implementation. The most common vulnerability is the “alg none” attack: an attacker changes the token header to “alg”: “none”, and the server, without verifying the algorithm, accepts the forged token. Protection: always verify that the algorithm in the header matches the expected one (RS256, ES256), and reject tokens with alg: none.
JWT vulnerabilities also include: weak secret key for HMAC (brute-force in minutes), private key leakage (signing any data on behalf of the server), storing sensitive data in the payload (JWT does not encrypt, only signs), JWK header injection attack (injecting a custom public key). Using trusted libraries — Nimbus JOSE + JWT, jjwt (io.jsonwebtoken), PyJWT — reduces the risk of exploiting these vulnerabilities.
An additional security measure is JWK Thumbprint (RFC 7638): binding a public key to the token via a thumbprint in the header. If the server stores the expected thumbprint for each client, JWK header injection becomes impossible — the server rejects any key that does not match the registered one. The OAuth Security Workshop 2025 recommends JWK Thumbprint as mandatory protection for all JWTs used in financial and medical applications.
The jjwt library (auth0/java-jwt) allows creating and verifying JWTs in an Android application in just a few lines. In the example below, the server generates a token with sub and role, and the client verifies the signature. For secure storage of the secret key on the server, use environment variables or an HSM (Hardware Security Module) — storing the key in code or a configuration file is a serious security mistake.
val secret = "my-256-bit-secret-key-here"
val token = JWT.create()
.withSubject("user-abc-123")
.withIssuer("auth.example.com")
.withClaim("role", "premium_user")
.withExpiresAt(Date(System.currentTimeMillis() + 3600000))
.sign(Algorithm.HMAC256(secret))
// Sending token to client
println("JWT: $token")
fun verifyToken(token: String): Boolean {
return try {
val decoded = JWT.require(Algorithm.HMAC256(secret))
.withIssuer("auth.example.com")
.build()
.verify(token)
// Signature valid, claims extracted
println("Subject: ${decoded.subject}")
true
} catch (e: Exception) {
println("Token invalid: ${e.message}")
false
}
}
Frequently Asked Questions
No. JWT is signed, not encrypted — anyone can decode the Base64 payload and read the data. Sensitive information (passwords, credit card numbers, personal data) must be transmitted only in encrypted form using JWE (JSON Web Encryption).
ES256 (ECDSA with P-256) is recommended — it provides an equivalent level of security to RSA 2048-bit with a significantly smaller signature size. RS256 is suitable for compatibility with legacy systems. HS256 (HMAC) requires secure exchange of a secret key, which is more challenging in a distributed architecture.
JWT cannot be revoked directly — it is valid until exp. Solutions: use a short lifetime (15–30 minutes), maintain a blacklist of revoked jti (JWT ID) values on the server, or bind tokens to a secret key version. The refresh token is revoked in the standard way — by removing it from storage.
Bearer token is a concept: any token that the bearer can use for access. JWT is a specific token format. A Bearer token can be a JWT or an opaque string. JWT adds self-containment and cryptographic verification to the Bearer concept.
A typical JWT with RS256 signature is 500–2000 bytes. If the payload contains many custom claims or an asymmetric signature with a large key is used, the size can reach 4–5 KB. This is significantly larger than a session token (16–64 bytes), which affects HTTP header size.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also