HIGH session fixationaspnetbearer tokens

Session Fixation in Aspnet with Bearer Tokens

Session Fixation in Aspnet with Bearer Tokens — how this specific combination creates or exposes the vulnerability

Session fixation in ASP.NET when Bearer Tokens are used occurs when an attacker forces a victim to authenticate with a token the attacker knows or can predict. This is relevant when an application issues bearer tokens (e.g., JWTs in Authorization: Bearer) and also relies on server-side session identifiers for authorization decisions or anti-forgery checks. If an endpoint accepts a token but does not issue a fresh token or bind the token to a newly established server-side session, an attacker can set or obtain a token and trick a victim into using it.

Consider a scenario where an ASP.NET app exposes an endpoint like /api/login?token=known that accepts a bearer token via query string or header and logs the user in without rotating the token. An attacker crafts a link with a fixed token and sends it to a victim. When the victim visits the link while authenticated (or tricks the victim into authenticating), the server associates the known token with the victim’s session. Because the token is reused and not regenerated after authentication, the attacker can reuse the same token to act as the victim. Even if the application uses cookie-based anti-forgery tokens, mixing predictable or shared bearer tokens with session state can bypass expected CSRF protections.

Another variant involves token leakage through logs, browser history, or referrer headers when tokens are embedded in URLs. If the ASP.NET app uses bearer tokens for API authentication but also maintains a session identifier on the server (e.g., via cookies), an attacker who obtains a token can correlate it with session fixation risks. For example, an endpoint that issues access tokens without validating a pre-existing session or rotating tokens on privilege changes can enable fixation across authentication boundaries. This is especially dangerous when token binding to client context (IP, user-agent) is missing or inconsistently enforced.

Real-world patterns include OAuth flows where the client receives a token but the server-side session remains unchanged after login, or Single Page Applications that store bearer tokens in local storage and rely on server-side sessions for authorization. If the server maps an incoming bearer token to a session ID without validating that the mapping was freshly established, an attacker can fixate a session by influencing the token-session association before authentication.

Common misconfigurations that exacerbate the issue include accepting bearer tokens via query parameters, failing to issue a new token after successful authentication, not binding tokens to a server-side session identifier, and missing strict token validation (audience, issuer, expiration). These create conditions where session fixation can occur despite the presence of bearer tokens, undermining the security boundary between authentication and session management.

Bearer Tokens-Specific Remediation in Aspnet — concrete code fixes

Remediation focuses on ensuring token rotation, strict validation, and separation of concerns between bearer tokens and server-side sessions. Below are concrete examples for an ASP.NET Core application using JWT Bearer authentication.

1. Always issue a new token after authentication and avoid accepting tokens via query strings.

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

app.MapPost("/api/login", async (HttpContext context, YourDbContext db) =>
{
    var username = context.Request.Form["username"];
    var password = context.Request.Form["password"];
    var user = await ValidateUserAsync(db, username, password);
    if (user == null)
    {
        context.Response.StatusCode = 401;
        return;
    }
    // Rotate token: do not reuse an incoming token
    var newToken = GenerateJwtToken(user);
    context.Response.Cookies.Append("Authorization", $"Bearer {newToken}", new CookieOptions
    {
        HttpOnly = true,
        Secure = true,
        SameSite = SameSiteMode.Strict
    });
    return Results.Json(new { token = newToken });
});

2. Configure JWT Bearer options to reject tokens from query strings and enforce token binding where possible.

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = "https://your-identity-provider";
        options.Audience = "api1";
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            // Do not allow tokens from query strings to reduce leakage risk
            NameClaimType = ClaimTypes.NameIdentifier
        };
        // Enforce HTTPS and avoid saving tokens in URLs
        options.RequireHttpsMetadata = true;
    });

3. Bind tokens to a server-side session or a per-authentication nonce to prevent fixation. Store a mapping in a secure, short-lived cache keyed by session identifier.

services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = Configuration.GetConnectionString("Redis");
});

// After validating the token, associate it with a session nonce
app.Use(async (context, next)
{
    var token = context.Request.Headers["Authorization"].ToString()?.Replace("Bearer ", "");
    if (!string.IsNullOrEmpty(token))
    {
        var sessionId = context.Session.Id; // Ensure session is established
        var nonce = Guid.NewGuid().ToString();
        // Store mapping with expiration slightly longer than token lifetime
        await cache.SetStringAsync($"token:{sessionId}", token, new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
        });
        context.Items["TokenBinding"] = nonce;
    }
    await next();
});

4. Avoid accepting bearer tokens in URLs and prefer headers. Ensure tokens are not logged or exposed in browser history.

// Do not do this:
// GET /api/resource?token=abc123

// Instead, require header usage:
// Authorization: Bearer abc123
// Configure your client and server to reject tokens in query strings.

5. Rotate tokens on privilege changes and re-validate binding on sensitive operations.

app.MapPost("/api/change-role", async (HttpContext context, YourDbContext db) =>
{
    var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    // Perform role change
    // Invalidate previous tokens and issue a new one
    var newToken = GenerateJwtTokenForUser(userId);
    context.Response.Cookies.Append("Authorization", $"Bearer {newToken}", new CookieOptions
    {
        HttpOnly = true,
        Secure = true,
        SameSite = SameSiteMode.Strict
    });
    return Results.Ok();
});

These measures ensure that bearer tokens are not reused across sessions, are transmitted securely, and are bound to fresh session contexts, mitigating session fixation risks in ASP.NET applications.

Frequently Asked Questions

Can middleBrick detect session fixation risks when Bearer Tokens are used?
Yes. middleBrick scans unauthenticated attack surfaces and includes checks that can surface token handling and session binding issues; findings include remediation guidance.
Does middleBrick fix the vulnerabilities it finds?
No. middleBrick detects and reports findings with remediation guidance. It does not fix, patch, block, or remediate issues.