CodeToClarity Logo
Published on ·14 min read·Security

How JWT (JSON Web Token) Works: Simple Guide for Developers

Kishan KumarKishan Kumar

Learn what JWT (JSON Web Token) is, how it works, and why it's the go-to method for secure, stateless authentication in modern web apps and APIs.

If you have spent any time building modern web applications, you have likely encountered the challenge of user authentication. We want our applications to know exactly who is making a request, and we want to ensure that malicious actors cannot impersonate our legitimate users. In the early days of web development, the solution was relatively straightforward. We used server-side sessions. But as our applications grew, moved into the cloud, and split into microservices, the traditional session-based approach began to show its limitations.

Enter JSON Web Tokens, commonly known as JWTs.

Whether you are building a React frontend communicating with an ASP.NET Core minimal API, or a mobile application connecting to a sprawling mesh of microservices, JWTs have become the standard methodology for modern, stateless authentication. In this comprehensive guide, we are going to break down exactly what a JWT is, how it works under the hood at a fundamental level, and how you can implement a secure, highly scalable authentication system in your ASP.NET Core applications.


The Bottleneck of Traditional Sessions

Before we dive into the solution, we need to completely understand the problem. Imagine you run a traditional, old-school hotel. When a guest checks in, the receptionist writes their name and assigned room number into a large backend registry ledger, and hands the guest a generic physical room key. Every single time that guest wants to enter their room, use the fitness center, or eat at the breakfast buffet, a staff member has to call the receptionist and ask if the person holding key number forty-two is currently allowed to be there.

In the web development world, this is how stateful session authentication functions. The server creates a unique session identifier upon login, saves user details in its server memory or a backend database, and gives the browser a session ID cookie. When the browser makes subsequent requests, it sends that session ID back. The server then takes that ID, pauses everything to look up the session data in its database, and finally decides if the action is allowed.

This architectural pattern creates a few major bottlenecks. First, it requires continuous database lookups for almost every single authorized request. This radically degrades API performance under heavy load. Second, it makes horizontal scaling incredibly difficult. If your application becomes highly popular and you need to spin up five different backend server instances behind a load balancer, they all need to share that single source of truth for sessions. If a user logs into Server A, Server B also needs to know about it. This frequently forces developers to implement complex, external distributed caching solutions like Redis simply to handle basic login state.

There has to be a more efficient way to verify identity.


The Modern Hotel Keycard Analogy

Now imagine a modernized, tech-forward hotel. When a guest checks in, they present their identification. The receptionist verifies their identity exactly once during the entire stay, and then encodes a smart digital keycard. This keycard cryptographically states that this guest is permitted to open Room 505, they are allowed pool access, and their credentials expire tomorrow at exactly noon. The receptionist then digitally signs this data to prove the hotel formally issued it.

When the guest taps their keycard on the fitness center gate, the electronic gate does not pick up a phone to call the reception desk. The gate simply reads the data on the keycard, verifies the cryptographical footprint from the hotel, and opens the door. The gate is completely self-sufficient.

This is the exact core concept of stateless authentication.

A JSON Web Token is a compact, self-contained method for securely transmitting information between a client system and a backend server as a JSON object. This information can be explicitly verified and trusted because it is digitally signed to prevent tampering. When your frontend application initially authenticates with an API using a username and password, the API validates the credentials and returns a generated JWT. For all subsequent API operations, the frontend sends this token back to the server. The server looks at the token string, verifies its signature via a mathematical algorithm, and immediately knows who the user is without ever querying a relational database.

Comparison between traditional stateful server sessions versus modern stateless localized JWT authentication
Comparison between traditional stateful server sessions versus modern stateless localized JWT authentication

Demystifying the Anatomy of a JSON Web Token

To truly grasp how this operational flow works, we need to look closer. If you inspect a raw JWT, it looks like a long, chaotic string of random characters that typically starts with the letters "eyJ". But it is actually a highly structured string divided into three distinct parts, with each part separated by period characters.

The format is organized as: Header.Payload.Signature

Let us break down each exact component and its responsibilities.

Anatomy of a json web token architecture showing core header, payload, and signature segments
Anatomy of a json web token architecture showing core header, payload, and signature segments

1. The Header Subcomponent

The header typically consists of two distinct data points. It identifies the type of the token, which is uniformly defined as JWT. It also specifies the underlying signing algorithm being used, such as HMAC SHA256 or RSA.

{
  "alg": "HS256",
  "typ": "JWT"
}

This tiny JSON object is then Base64Url encoded to form the very first part of the JWT string. Its sole purpose is to instruct the receiving server on what specific algorithm to apply when verifying the token later.

2. The Payload Data

The second part of the string represents the payload, which contains the descriptive claims. Claims are formal statements about an entity (typically, your user) and any other necessary supplementary data. Claims are generally categorized into standard registered claims, public claims, and private custom claims.

Registered claims are predefined standards that are not mandatory but highly recommended. These provide a set of useful, interoperable identifiers. Examples include "sub" for the subject or user ID, "iss" for the token issuer, and "exp" for the expiration timestamp. Private claims are the custom values created to share information specific to your application, such as user roles or subscription tiers.

{
  "sub": "user_123456",
  "role": "Administrator",
  "name": "CodeToClarity Reader",
  "iat": 1713000000,
  "exp": 1713086400
}

Exactly like the header element, the JSON payload is also Base64Url encoded to form the middle section of the JWT. The "iat" claim defines when the token was formally issued, while the "exp" claim dictates the exact moment the token's lifecycle terminates.

A critical security warning must be noted here. Base64Url encoding is not encryption by any definition. It is merely a transport mechanism used to convert raw binary data into a string format that can be easily transmitted through HTTP headers without breaking the payload structure. Absolutely anyone who intercepts your JWT can parse and read the payload text. You should never, under any circumstances whatsoever, place sensitive information like unencrypted passwords, personal social security numbers, or internal database connection strings inside a JWT payload component. If you wish to observe this behavior in action, you can paste any valid JWT into the official JSON Web Token architecture debugger and instantly read its internal contents.

3. The Cryptographic Signature

The signature is the component where the real magic happens. To create this mathematical signature part, the server takes the encoded header, the encoded payload, a highly secure secret string that only the server knows, and the hashing algorithm specified in the original header. It then combines and signs them together.

If you have chosen the HMAC SHA256 algorithm, the signature calculation is executed in this precise manner:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your_ultra_secure_server_secret
)

This produced signature is fundamentally and mathematically bound to the exact contents of the header and payload. If a malicious actor intercepts the JSON Web Token and attempts to manually change their role in the payload from standard user to administrator, the base64 string text will change. When the backend server receives this altered token on the next request, it will dutifully recalculate the expected signature using its hidden secret key. The newly calculated result will absolutely not match the signature appended on the inbound token string. The server immediately recognizes that the token was criminally tampered with and immediately rejects the API request with a 401 Unauthorized status.


Symmetric vs Asymmetric Algorithms: HS256 vs RS256

While we used HS256 in our conceptual example, developers frequently face the choice of which signing algorithm to leverage. The decision typically boils down to symmetric versus asymmetric capabilities.

HS256 relies on a symmetric cryptography implementation natively. This means the exact same secret key is used to both generate the token and validate the token. This works flawlessly for monolithic applications or smaller APIs where a single server handles everything. However, propagating that same secret key across fifty different microservices becomes a very stressful security liability.

RS256 fixes this by utilizing asymmetric cryptography. Under this paradigm, a private key is securely locked away and used exclusively to generate and sign the token. Meanwhile, a corresponding public key is widely distributed to all your microservices. The microservices can use this public key to successfully confirm the token's signature, but they inherently lack the mathematical ability to create new fake tokens. This vastly minimizes your attack surface vector in enterprise environments.


Why Software Engineers Love the Token Approach

There are profound reasons the industry has heavily gravitated towards this identity model.

First, performance optimization. Bypassing database lookups for complex authentication logic on every single API request significantly decreases overall response latency, thereby giving your front-end users a brilliantly snappier experience.

Second, cross domain dominance. Traditional HTTP cookies natively tie your application to a single root domain architecture. If your React frontend is hosted on one domain and your distinct backend API is hosted on completely separate infrastructure, sharing authentication cookies introduces deeply frustrating cross origin resource sharing constraints. Because JWTs are standardly transmitted cleanly inside the HTTP Authorization request header, they bypass domain-specific restrictions efficiently.


Building the Backend: Implementing Authentication in ASP.NET Core

Now that we possess a rock-solid understanding of the underlying theory, let us write some implementation code. We are going to establish basic authentication in a professional ASP.NET Core web application.

Step 1: Install the Necessary Integration Packages

ASP.NET Core has fantastic built-in fundamental support for token mechanics, but we still need to aggressively pull in the official middleware plugin to handle bearer token protocols seamlessly.

Open your integrated terminal console and install the following standard dependency:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

You can read the deep technical documentation regarding this specific asset on its official NuGet package repository.

Step 2: Configure Dedicated Application Settings

Your server runtime requires a secret key string to mathematically calculate and sign outbound tokens. It also desires to formally know the designated Issuer (who precisely created the token server) and Audience (who exactly is intended to consume the token client). We will store these configurations locally in the appsettings.json configuration file.

{
  "JwtConfiguration": {
    "SecretKey": "ThisIsMySuperSecureLongSecretKeyThatShouldBeStoredInEnvironmentVariablesNow",
    "Issuer": "https://api.codetoclarity.in",
    "Audience": "https://app.codetoclarity.in"
  }
}

In any authentic production environment, you should never hardcode your secret string directly inside your configuration JSON files. You must always source it through secured Environment Variables or an enterprise secret manager vault. Your key string must be extremely long and cryptographically complex to prevent brute force cracking attempts.

Step 3: Register Authentication Architectural Services

Navigate into your primary Program.cs file. We have to instruct the foundational ASP.NET Core dependency injection container that we implicitly want to leverage the bearer token scheme.

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

// Read designated configurations securely
var jwtSettings = builder.Configuration.GetSection("JwtConfiguration");
var secretKey = jwtSettings["SecretKey"];

// Configure Core Authentication
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = jwtSettings["Issuer"],
        ValidAudience = jwtSettings["Audience"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey))
    };
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.Run();

Let us meticulously review what the TokenValidationParameters block is executing. This comprehensive configuration object tells the underlying framework exactly how strictly it should evaluate all inbound data credentials. We instruct the system to stringently validate the issuer and audience records to ensure the token has not been maliciously hijacked for an improper system context. We decisively require validating the lifetime expiration stamp to cleanly reject expired sessions. Most importantly, we mandate calculating the symmetric signing key. For developers looking to research advanced configurations, the official Microsoft documentation on configuration parameters provides exceptional supplemental material.

Step 4: Creating the Execution Token Service

When an unauthenticated user successfully submits their secure password, we desperately need a utility service to formulate their new payload credentials. Let us create a dedicated class logically named CodeToClarityTokenService.

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;

public class CodeToClarityTokenService
{
    private readonly IConfiguration _configuration;

    public CodeToClarityTokenService(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public string GenerateAccessToken(string userId, string customRole)
    {
        var jwtSettings = _configuration.GetSection("JwtConfiguration");
        var secretKey = jwtSettings["SecretKey"];

        // Define the explicit claims grouping for the payload 
        var claims = new List<Claim>
        {
            new Claim(JwtRegisteredClaimNames.Sub, userId),
            new Claim("UserRole", customRole),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
        };

        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        // Assemble the token descriptors accurately
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(claims),
            Expires = DateTime.UtcNow.AddMinutes(15), 
            Issuer = jwtSettings["Issuer"],
            Audience = jwtSettings["Audience"],
            SigningCredentials = credentials
        };

        var tokenHandler = new JwtSecurityTokenHandler();
        var rawTokenData = tokenHandler.CreateToken(tokenDescriptor);

        return tokenHandler.WriteToken(rawTokenData);
    }
}

Notice the specific configuration to dictate expiration accurately fifteen minutes into the future? We will thoroughly explore why brief expiration lifespans are fundamentally essential in the best practices segment ahead.

Step 5: Securing the Outward Facing API Endpoints

With our pipeline middleware fully registered, securing operational endpoints becomes astonishingly simple and brief. By explicitly applying the standard authorize attribute to any existing controller, ASP.NET Core will automatically interrogate raw incoming requests. It actively searches the standard HTTP headers, safely extracts the bearer format token, mathematically computes its underlying signature, and automatically maps the designated claims straight to your current context controller environment.

[ApiController]
[Route("api/[controller]")]
public class DashboardController : ControllerBase
{
    [HttpGet("protected-metrics")]
    [Authorize]
    public IActionResult GetSecureDashboardData()
    {
        // Because of the authorize attribute parameter, we only process this
        // specific logic context if the inbound request holds a secure token.
        
        var userIdentity = User.FindFirstValue(ClaimTypes.NameIdentifier);
        return Ok(new { Message = $"Welcome cleanly to the secured internal dashboard, user profile {userIdentity}." });
    }
}

The Dark Side of State (Security Best Practices)

While these tokens are profoundly powerful assets, their disconnected and inherently stateless nature introduces distinct security paradigms.

Defending Against Hijacking Invasions

If a maliciously motivated user successfully intercepts a valid token currently being transmitted over a public network, they effectively assume the user identity fully. Because the issued token functionally represents the actual identity, there exists no secondary server intervention checks required natively. This concrete reality means uniformly enforcing the HTTPS protocol across your entire infrastructure backbone is absolutely nonnegotiable. Without transport layer cryptography, your structural tokens are plainly visible to anyone passively wiretapping the transit connection logic.

Resolving the Complex Logout Dilemma

One of the most frequently confusing architectural obstacles for beginners involves processing logouts. Because the backend environment does not store active session records natively, the central server has no immediate conception of an active user list. When an honest user clicks their logout button, the only viable action available is permanently deleting the token data locally from the frontend browser configuration. Unfortunately, the token string logic remains technically valid mathematically until it organically reaches its actual expiration timestamp. If an aggressive attacker previously replicated that specific token, they retain unauthorized backend access despite the original user logging out.

Embracing Maximum Efficiency with Refresh Systems

The modern, comprehensive architectural solution addressing the complicated logout and persistence framework is establishing incredibly brief initial lifespans assigned to the main data access tokens. The universally standard configuration is firmly limiting initial data tokens to a window of roughly fifteen continuous minutes.

Since organic humans absolutely refuse to continuously retype their password phrases four times every single hour, professional applications gracefully implement the dedicated Refresh Token structural pattern. When an authentic baseline login processes successfully, the underlying backend server dynamically formulates two distinct assets. The server yields the fast fifteen-minute token alongside a permanent, long lived randomized string conventionally identified as a refresh token string. This refresh footprint is meticulously archived securely within a stateful relational database table.

When the fifteen-minute payload string automatically expires, the integrated frontend logic silently transmits the refresh token asset to a discrete specialized endpoint process. The background server then quickly yields a brand new active token string, permitting the workflow logic to seamlessly continue running. If a user wishes to definitively revoke access, or if you heavily suspect a serious security breach event, the administrative console logic rapidly deletes the single associated refresh token from the database directly. Any existing attacker retains brief payload access exclusively until the currently active primary token forcefully destructs momentarily, drastically narrowing your overall infrastructure vulnerability timeline.

Sequence flow architecture demonstrating access token expiration paired with refresh token backend revalidation
Sequence flow architecture demonstrating access token expiration paired with refresh token backend revalidation

Concluding Thoughts on Stateless Frameworks

Transitioning into completely stateless authentication patterns ultimately represents a massive architectural pivot regarding how developers must engineer dynamic ecosystems. By intentionally migrating logical authentication parameters dynamically towards edge systems and explicitly relying upon cryptography formats, developers rapidly unlock extreme optimization ceilings alongside robust microservice capabilities seamlessly.

We comprehensively mapped exactly how internal token environments systematically execute, constructed a dependable middleware structure properly embedded inside ASP.NET Core paradigms, and intensively illuminated specialized structural architecture rules to aggressively defend genuine users. While the starting engineering overhead frequently initially appears highly daunting mathematically compared to obsolete backend cookie methods, the intrinsic runtime advantages inevitably command unmatched value the immediate moment the baseline system structure demands intense performance capacity.

Kishan Kumar

Kishan Kumar

Software Engineer / Tech Blogger

LinkedInConnect

A passionate software engineer with experience in building scalable web applications and sharing knowledge through technical writing. Dedicated to continuous learning and community contribution.