When a user logs in, the server needs to remember who they are on every request. One way is sessions, but that means storing data on the server. JWT solves this differently — all the user info like user ID, roles, and expiry is packed into the token itself, so the server just verifies the signature on each request without hitting the database every time.
A JWT, or JSON Web Token, is made of three parts joined by dots: Header, Payload, and Signature.
header.payload.signature
- The Header tells us which algorithm was used to sign the token — like HS256 or RS256 — along with the token type.
-
The Payload holds the actual user data, called "claims" — things like the user's ID, their role, when the token was issued (iat), and when it expires (exp). Common claims are:
sub → Subject (user ID)
iss → Issuer
aud → Audience
exp → Expiration time
iat → Issued at The Signature is what keeps the token trustworthy.
To create the Signature, the server takes the Base64-encoded Header and Payload, joins them, and runs them through a hashing function along with a secret key. Think of it like:
Header + Payload = message
message + key = signature.
This signature is then attached to the token.
When the user sends the token back, the server needs to verify it. There are two ways depending on the algorithm.
1: With HS256 (symmetric) : During request validation, the server takes the message from the token, uses the same secret to generate a new signature, and compares it with the signature inside the token.
Let's understand with an analogy:
Assume:
secret = 3 & Signing rule is : multiplication by secret
signature = message × secret
During token generation
Assume
message = 10
signature = 10 * 3 = 30
So token ---> 10.30
During Token Validation (Coming User Request (with token))
Assume coming token ---> 10.30
Application will recalculate signature using the token's message:
signaure(Coming User Request) = 10 * 3 = 30
Now compare this signature with the signature attched in the token
signature(Request User's 30) == signature calculated (30)
30 == 30 --> True
✔ Token is valid.
Now, assume, attacker modified the message(Token)
Assume coming token ---> 18.30
Application will recalculate signature using the token's message:
signaure(Request) = 18 * 3 = 36
But the signature is 30, so
signature(User's 36) != signature (30)
❌ Token is Invalid.
But what if we don't want to share the same secret key everywhere? That's where RS256 comes in.
2: With RS256 (asymmetric): The server(Application) signs using a private key, but verification is done using a public key. This is useful in microservices where multiple services need to verify tokens without ever seeing the private key.
There are two keys:
Private key → used to SIGN
Public key → used to VERIFY
Only the Identity Provider has the private key. Your API only has the public key.
🔐 Signing Process (Token Generation)
Assume a very simple fake math model:
Private key operation = multiply by 3
Public key operation = divide by 3
(Note: This is NOT real RSA — just to understand concept.)
Assume the message generated is 10
message = 10
Now Sign Using Private Key
signature = message × 3
signature = 10 × 3 = 30
So full token conceptually: 10.30
Verification Process:
Requested token: 10.30
message = 10 & signature = 30
public key = divide by 3
Now it verifies:
expected_message = signature ÷ 3 = 30 ÷ 3 = 10Now compare:
expected_message == original message?
10 == 10 → TRUE
✔ Token is valid.
Let's see if the message (token) is modified"
Case 1: Attacker modified the message
Token Received: 20.30, but original token was 10.30
so recieved message = 20 & signature = 30
Verification:
expected_message = signature ÷ 3 => 30 ÷ 3 = 10
Compare:
10 == 20 → FALSE
❌ Token is Invalid.
Case 2: Attacker/User Used the wrong Public Key
Suppose used public key = divide by 5 (original was divided by 3)
Requested token: 10.30
Then:
expected_message = 30 ÷ 5 = 6
Compare:
6 == 10 → FALSE
❌ Invalid
This is why even if someone steals your token and changes the user ID to get admin access, it won't work. The signature will fail verification.
One important thing to remember. The payload is only Base64 encoded, not encrypted. Anyone can decode and read it. So never store sensitive data like passwords inside a JWT. The signature only proves the token wasn't tampered with; it does not hide the data.
Top comments (0)