Are Your ASPNET Core Tokens Exposing Your Entire System? 6 Security Tokens You’re Probably Misconfiguring! + Video

Listen to this Post

Featured Image

Introduction

Security tokens are the silent gatekeepers of modern web applications, yet they remain one of the most misconfigured pillars of ASP.NET Core development. From stateless JWTs that can’t be revoked to forgotten CSRF defences that leave APIs wide open, a single oversight can turn a scalable architecture into a breach waiting to happen. This article dissects the six token types every .NET developer must master, provides hardened implementation steps, and reveals how attackers exploit common pitfalls.

Learning Objectives

  • Differentiate the purpose and lifecycle of JWT, Access, Refresh, ID, Session, and CSRF tokens in ASP.NET Core.
  • Implement secure JWT authentication with proper signing key storage, short-lived access tokens, and refresh token rotation.
  • Harden stateful and stateless token mechanisms against revocation gaps, XSS, CSRF, and replay attacks.

You Should Know

  1. JWT (JSON Web Token) – Stateless Power, Revocation Pain
    JWTs are self‑contained; the server trusts the signature without touching a database. This makes them ideal for microservices, but revocation becomes non‑trivial.

Step‑by‑step: Configure JWT in ASP.NET Core with RSA signing and a blocklist

 Create a new Web API project
dotnet new webapi -n SecureTokenApi
cd SecureTokenApi
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Generate an RSA private key (Linux/macOS):

openssl genrsa -out private.key 2048
openssl rsa -in private.key -pubout -out public.key

In Program.cs, load the public key and configure JWT Bearer:

using Microsoft.IdentityModel.Tokens;
using System.Security.Cryptography;
using System.Text;

var rsa = RSA.Create();
rsa.ImportFromPem(File.ReadAllText("public.key").ToCharArray());

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "your-issuer",
ValidateAudience = true,
ValidAudience = "your-audience",
ValidateLifetime = true,
IssuerSigningKey = new RsaSecurityKey(rsa),
ValidateIssuerSigningKey = true
};
});

Revocation workaround – blocklist middleware

Inject a distributed cache (Redis) and check if the JTI (JWT ID) is present on each request.

app.Use(async (context, next) =>
{
var jti = context.User.FindFirst(JwtRegisteredClaimNames.Jti)?.Value;
if (jti != null && await cache.GetStringAsync($"revoked_{jti}") != null)
{
context.Response.StatusCode = 401;
return;
}
await next();
});
  1. Access Token – Short‑Lived Credential with Bearer Semantics
    Access tokens are the key to your API; they must be extremely short‑lived (minutes, not hours).

Step‑by‑step: Issue a JWT‑based access token

var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userId),
new Claim("role", "admin"),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
}),
Expires = DateTime.UtcNow.AddMinutes(15), // short lived!
Issuer = "your-issuer",
Audience = "your-audience",
SigningCredentials = new SigningCredentials(
new RsaSecurityKey(privateRsa),
SecurityAlgorithms.RsaSha256)
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
var accessToken = tokenHandler.WriteToken(token);

Never store access tokens in localStorage (XSS risk). Use `HttpOnly` cookies for browser‑based clients.

3. Refresh Token – Long‑Lived Lifeline with Rotation

A refresh token must be a cryptographically strong random string, stored server‑side (hashed) and rotated each time it is used.

Step‑by‑step: Refresh token rotation in ASP.NET Core

public async Task<IActionResult> Refresh(string refreshToken)
{
var storedHash = _dbContext.RefreshTokens
.Where(rt => rt.TokenHash == Hash(refreshToken) && rt.Expires > DateTime.UtcNow)
.FirstOrDefault();
if (storedHash == null) return Unauthorized();

// rotate: invalidate old token, issue new pair
_dbContext.RefreshTokens.Remove(storedHash);
var newRefreshToken = GenerateSecureRefreshToken();
_dbContext.RefreshTokens.Add(new RefreshToken
{
TokenHash = Hash(newRefreshToken),
Expires = DateTime.UtcNow.AddDays(7),
UserId = storedHash.UserId
});
await _dbContext.SaveChangesAsync();

var newAccessToken = GenerateAccessToken(storedHash.UserId);
return Ok(new { access_token = newAccessToken, refresh_token = newRefreshToken });
}

Use `RandomNumberGenerator` (not `Guid` or Random) for token generation:

string GenerateSecureRefreshToken()
{
var bytes = new byte[bash];
RandomNumberGenerator.Fill(bytes);
return Convert.ToBase64String(bytes);
}
  1. ID Token – Identity Proof in OpenID Connect
    Unlike access tokens, ID tokens are meant for the client application to verify the user’s identity. They should never be sent to APIs.

Step‑by‑step: Validate ID token signature in a .NET client

var keys = await _httpClient.GetFromJsonAsync<JsonWebKeySet>(
"https://your-identity-server/.well-known/openid-configuration/jwks");
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "https://your-identity-server",
ValidateAudience = true,
ValidAudience = "your-client-id",
IssuerSigningKeys = keys?.GetSigningKeys(),
ValidateLifetime = true
};
var handler = new JwtSecurityTokenHandler();
var principal = handler.ValidateToken(idToken, validationParameters, out _);

Always enforce HTTPS and check the `nonce` claim to prevent replay.

  1. Session Token – Stateful Simplicity with Server‑Side Control
    ASP.NET Core session uses a cookie containing only a session ID; all data stays on the server.

Step‑by‑step: Secure session configuration

dotnet add package Microsoft.AspNetCore.Session

In `Program.cs`:

builder.Services.AddDistributedMemoryCache(); // use Redis in production
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(20);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
app.UseSession();

Store sensitive data encrypted – never place secrets in session in plaintext. Use IDataProtector.

  1. CSRF Token – The Silent Guardian Against Cross‑Site Requests
    Modern browsers automatically attach cookies, making stateful apps (and even cookie‑borne JWTs) vulnerable to CSRF. ASP.NET Core’s anti‑forgery middleware blocks forged requests.

Step‑by‑step: Enable anti‑forgery globally

builder.Services.AddAntiforgery(options => 
{
options.HeaderName = "X-CSRF-TOKEN";
options.FormFieldName = "__RequestVerificationToken";
options.Cookie.Name = ".AspNetCore.Antiforgery";
options.Cookie.HttpOnly = false; // must be readable by JavaScript
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});

In your controllers:

[bash]
[bash]
public IActionResult UpdateProfile(ProfileModel model) { ... }

For APIs accepting cookies, require a custom header (e.g., X-CSRF-TOKEN) that cannot be set cross‑origin. For SPA + API separation, issue a CSRF token via a dedicated endpoint and send it in the request header.

What Undercode Say

  • Key Takeaway 1: Stateless JWTs offer scalability but demand a complementary revocation mechanism (blocklist or short expiry + refresh rotation). Ignoring revocation is the root cause of countless privilege escalation incidents.
  • Key Takeaway 2: The battle between localStorage and HttpOnly cookies for token storage is settled: for browser‑based apps, HttpOnly cookies with `SameSite=Strict` and CSRF protection are the only way to mitigate XSS token theft.

Analysis: The six tokens are not interchangeable; each serves a distinct security boundary. Yet many developers blur the lines—using JWTs as session tokens, storing refresh tokens unprotected, or forgetting that ID tokens are for clients, not resources. The comment by Javed Khan correctly highlights the revocation dilemma; his community question on token storage reflects a real‑world confusion that leads directly to breaches. Modern frameworks like ASP.NET Core provide the knobs, but they default to insecure or incomplete settings—explicit hardening is mandatory. The shift toward OAuth2 / OIDC has reduced home‑grown crypto disasters but introduced misconfigurations in token lifetimes, signing algorithms (e.g., `None` algorithm attacks), and audience validation. Automated scanners now routinely check for `/.well-known/openid-configuration` leaks; manual security reviews must validate every token endpoint.

Prediction

As passkeys and WebAuthn gain traction, the reliance on long‑lived refresh tokens may diminish, replaced by continuous authentication and device biometrics. However, the token‑centric architecture of OAuth2 will not disappear; instead, we will see tighter integration with Zero Trust—every API call re‑authenticated via short‑lived, context‑aware tokens that carry risk scores. Attackers will shift from stealing static tokens to compromising token issuance pipelines (e.g., misconfigured identity providers). Expect Microsoft to further harden ASP.NET Core defaults, perhaps deprecating symmetric signing keys in favour of asymmetric by default, and introducing built‑in revocation APIs for distributed JWT scenarios.

▶️ Related Video (78% Match):

🎯Let’s Practice For Free:

IT/Security Reporter URL:

Reported By: Abdulwaris1 Dotnetcore – Hackers Feeds
Extra Hub: Undercode MoN
Basic Verification: Pass ✅

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow UndercodeTesting & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin | 🦋BlueSky