In the previous article, we learned that after a successful login, an ASP.NET Core Web API generates a JWT and sends it to the client. The client then includes that token with every protected request.
But have you ever wondered what is actually inside that long string?
Many developers use JWT every day without understanding its structure. They know how to generate a token, but when authentication fails or a token is rejected, troubleshooting becomes difficult.
To use JWT confidently, you should first understand how it is built internally.
What Is Inside a JWT?
A JWT (JSON Web Token) is simply a string made up of three parts separated by dots (.).
Header.Payload.SignatureA real JWT looks something like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMDEiLCJlbWFpbCI6ImFqYXlAZXhhbXBsZS5jb20ifQ
.
N2xvTnB6a3Q4T0RtRzN...Each section has a different responsibility.
| Part | Responsibility |
|---|---|
| Header | Describes the token |
| Payload | Contains user information (claims) |
| Signature | Protects the token from modification |
Think of a JWT like an official ID card.
The Header tells you what kind of ID it is.
The Payload contains the person's details.
The Signature proves the ID was issued by a trusted authority and hasn't been changed.
Why Does JWT Have Three Parts?
Imagine a university issues student ID cards.
The card contains:
Student information
College name
Official security seal
If someone changes the student name or ID number, the security seal no longer matches.
JWT works in a similar way.
The first two parts store information, while the third part protects that information from unauthorized changes.
Part 1: Header
The Header contains metadata about the token.
A decoded header usually looks like this:
{
"alg": "HS256",
"typ": "JWT"
}Let's understand these properties.
| Property | Meaning |
|---|---|
| alg | Algorithm used to sign the token |
| typ | Token type |
The value:
HS256means the token is signed using the HMAC SHA-256 algorithm.
The header is small because its only job is to describe the token.
It does not contain user information.
Part 2: Payload
The Payload is the most important section of a JWT.
It contains information about the authenticated user.
Example:
{
"sub": "101",
"name": "Ajay",
"email": "[email protected]",
"role": "Customer",
"exp": 1784500000
}The values stored inside the payload are called Claims.
A claim is simply a piece of information about the authenticated user.
For example:
User ID
Name
Email
Role
Expiration time
The payload tells the API who the user is.
Understanding Claims
Claims are small pieces of identity information stored inside the JWT.
There are three common types of claims.
Registered Claims
These are standard claim names defined by the JWT specification.
Some common examples are:
| Claim | Purpose |
|---|---|
| sub | User identifier |
| iss | Token issuer |
| aud | Intended audience |
| exp | Expiration time |
| iat | Issued time |
| jti | Unique token identifier |
Using standard claims makes tokens easier for different systems to understand.
Public Claims
These are commonly agreed claim names used by applications.
Example:
{
"email": "[email protected]"
}Private Claims
Private claims are created by your own application.
Example:
{
"membership": "Gold"
}These claims are useful when your application needs additional information that isn't part of the standard specification.
Can Anyone Read the Payload?
Yes.
This is one of the biggest misconceptions about JWT.
The payload is Base64Url encoded, not encrypted.
That means anyone who has the token can decode it and view its contents.
For this reason, never store sensitive information inside the payload.
Avoid storing:
Passwords
OTPs
Credit card numbers
Bank details
API keys
Connection strings
Only include information that is necessary for authentication and authorization.
Part 3: Signature
The Signature is the security mechanism of JWT.
Its purpose is to detect whether someone has modified the token.
Conceptually, it is created like this:
HMACSHA256(
Header + Payload,
SecretKey
)Don't worry about the implementation yet.
For now, remember one important point:
The signature depends on both the token content and the secret signing key.
If either one changes, the signature also changes.
Internal Flow of JWT Validation
Suppose a client sends this request:
GET /api/profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...Before executing the API, ASP.NET Core performs a series of checks.
Receive JWT
↓
Read Header
↓
Read Payload
↓
Recalculate Signature
↓
Compare Signatures
↓
Validate Expiration
↓
Validate Issuer
↓
Validate Audience
↓
Request AcceptedIf any validation fails, the request is rejected.
Practical Example
Let's continue with our SecureShop API.
A customer logs in successfully.
The API returns a JWT.
The decoded payload looks like this:
{
"sub": "101",
"name": "John",
"email": "[email protected]",
"role": "Customer"
}Later, the customer requests:
GET /api/ordersThe JWT is sent with the request.
The API reads the payload to identify the user and validates the signature to ensure the token hasn't been modified.
Only after successful validation does the API continue processing the request.
Notice that the user's password is not included in the token.
The password is used only during login.
What Happens If Someone Changes the Token?
Imagine an attacker changes this payload:
{
"role": "Customer"
}to:
{
"role": "Admin"
}The payload now looks different.
However, the attacker cannot generate a valid signature because they don't have the secret signing key.
When the API validates the token, it recalculates the signature.
The signatures no longer match.
Result:
Token Validation FailedThe request is rejected immediately.
This is why modifying a JWT manually does not grant additional permissions.
Common Mistakes
Mistake 1: Thinking Base64 Encoding Means Encryption
Encoding and encryption are completely different.
Base64 encoding only changes the format of the data.
It does not hide the data.
Anyone can decode a JWT payload.
Mistake 2: Storing Sensitive Data
Sometimes developers store confidential information inside JWT claims.
For example:
{
"password": "Admin@123"
}or
{
"creditCard": "4111111111111111"
}This is a serious security mistake.
JWT should contain only the information required for identifying and authorizing the user.
Mistake 3: Assuming the Signature Encrypts the Token
The signature does not encrypt the payload.
Its purpose is to verify integrity.
It answers one question:
"Has this token been modified after it was issued?"
Key Takeaways
A JWT contains three parts: Header, Payload, and Signature.
The Header describes the token.
The Payload contains claims about the authenticated user.
The Signature protects the token from unauthorized modifications.
Claims store identity and authorization information.
The payload is encoded, not encrypted, so sensitive information should never be stored inside it.
ASP.NET Core validates the signature before trusting the token.
Now that you understand what a JWT looks like internally, the next step is learning how it travels through an ASP.NET Core Web API. We'll follow the complete authentication journey—from user login to accessing a protected endpoint—and see where JWT fits into the request pipeline.

Join the conversation! Your thoughts help the community grow.