JWT (JSON Web Token, pronounced "jot") is the most widely used authentication mechanism in modern APIs. Almost every app you test today — banking, e-commerce, social media — uses JWTs. If you do not understand JWTs, you cannot do serious API testing.
A JWT is a compact, self-contained token that carries information about the user. It is like a digitally signed ID card. Anyone can read what is on the card, but nobody can forge it because of the signature.
A JWT has three parts separated by dots. Each part is Base64-encoded JSON.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyNDI2MjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|___________________________|___________________________________________________________________________________________________|___________________________________|
HEADER PAYLOAD SIGNATURE| Part | Contains | Example (decoded) |
|---|---|---|
| Header | Algorithm + token type | { "alg": "HS256", "typ": "JWT" } |
| Payload | User data (claims) | { "sub": "1234567890", "name": "John Doe", "role": "admin", "exp": 1516242622 } |
| Signature | Verification hash | HMACSHA256(header + "." + payload, secret) |
Go to jwt.io right now. Paste any JWT and it will decode it. This is the most useful tool for API testers working with JWTs. Bookmark it.
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
public class JwtAuthTest {
private String accessToken;
@BeforeClass
public void loginAndGetToken() {
RestAssured.baseURI = "https://api.example.com";
// Step 1: Login and extract JWT
accessToken =
given()
.contentType(ContentType.JSON)
.body("""
{
"email": "testuser@example.com",
"password": "Test@1234"
}
""")
.when()
.post("/auth/login")
.then()
.statusCode(200)
.body("token", notNullValue())
.body("expiresIn", greaterThan(0))
.extract()
.jsonPath()
.getString("token");
System.out.println("Token obtained: " + accessToken.substring(0, 20) + "...");
}
@Test
public void testAccessProtectedResource() {
given()
.header("Authorization", "Bearer " + accessToken)
.when()
.get("/users/me")
.then()
.statusCode(200)
.body("email", equalTo("testuser@example.com"))
.body("role", notNullValue());
}
@Test
public void testNoTokenReturns401() {
given()
// No Authorization header
.when()
.get("/users/me")
.then()
.statusCode(401)
.body("error", containsString("unauthorized"));
}
@Test
public void testMalformedTokenReturns401() {
given()
.header("Authorization", "Bearer not.a.valid.token")
.when()
.get("/users/me")
.then()
.statusCode(401);
}
@Test
public void testExpiredTokenReturns401() {
// This is a real JWT but with exp set to a past date
String expiredToken = "eyJhbGciOiJIUzI1NiJ9"
+ ".eyJzdWIiOiIxIiwiZXhwIjoxNjAwMDAwMDAwfQ"
+ ".fakesignature";
given()
.header("Authorization", "Bearer " + expiredToken)
.when()
.get("/users/me")
.then()
.statusCode(401);
}
}Never log the full JWT in your test output. It is a credential. If someone gets your token, they can impersonate you until it expires. Log only the first 20 characters if you need to debug.
import java.util.Base64;
public class DecodeJwt {
public static void main(String[] args) {
String jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0IiwibmFtZSI6IlRlc3QgVXNlciIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxNjIzOTAyMn0.signature";
// Split into 3 parts
String[] parts = jwt.split("\\.");
// Decode header
String header = new String(Base64.getUrlDecoder().decode(parts[0]));
System.out.println("Header: " + header);
// Decode payload
String payload = new String(Base64.getUrlDecoder().decode(parts[1]));
System.out.println("Payload: " + payload);
// Signature cannot be decoded — it is a hash, not encoded data
System.out.println("Signature: " + parts[2]);
}
}Q: What is JWT and how is it different from session-based authentication?
A: JWT (JSON Web Token) is a self-contained token with three parts: header, payload, and signature. The payload contains user data (claims) like user ID, role, and expiry. In session-based auth, the server stores session data and gives the client a session ID cookie. The server must look up the session on every request. With JWT, the server does not store anything — all data is in the token itself. This makes JWT stateless and scalable. The tradeoff is that you cannot invalidate a JWT before its expiry without extra infrastructure like a blocklist.
Q: Can you read the data inside a JWT without the secret key?
A: Yes. The header and payload of a JWT are just Base64-encoded — not encrypted. Anyone can decode and read them. The signature is what prevents tampering. You need the secret key to CREATE or VERIFY a valid signature, but not to READ the payload. This is why you should never put sensitive data like passwords or credit card numbers in a JWT. Use jwt.io to decode any token.
Key Point: JWT has three parts: header.payload.signature. The payload carries user data (claims). Tokens expire. The signature prevents tampering. Use jwt.io to decode.