# Why ASP.NET Core Accepts Expired JWTs (And How to Fix It)

**Quick Answer:** ASP.NET Core accepts expired JWT tokens for up to five minutes past their expiration (`exp`) timestamp by default. This behavior is deliberate and compliant with RFC 7519, which permits clock skew leeway between servers. While Node.js, Python, and Go default to zero leeway, ASP.NET Core grants a default five-minute grace period.

Imagine you issue a JSON Web Token (JWT) with a strict expiration time of sixty seconds. You wait ninety seconds, fire a request at your ASP.NET Core backend, and to your surprise, it responds with a crisp `200 OK`. 

If you have ever encountered this behavior, your first instinct was probably to check whether your authentication middleware was completely broken. I have seen plenty of engineers run into this exact surprise. But this is not a bug—it is an intentional architectural decision baked straight into the JWT specification.

## Why does ASP.NET Core allow expired JWT tokens?

ASP.NET Core allows expired tokens because its default token validation parameters apply a five-minute clock skew leeway. This grace period prevents requests from failing due to minor time synchronization discrepancies between the machine that issued the token and the machine verifying it.

Think of it like agreeing to meet someone at a coffee shop at 2:00 PM. If your watch says 2:00 PM and their watch says 1:58 PM, you do not instantly cancel the meeting. You allow a buffer of a few minutes for imperfect clocks. 

In distributed architectures, servers face the exact same problem. Even with Network Time Protocol (NTP) syncing your infrastructure, clocks across different cloud providers, data centers, and containers drift by milliseconds or seconds. The official JWT specification (RFC 7519) explicitly accounts for this reality, allowing token checkers to grant a margin of leeway when validating timestamps like `exp` (expiration) and `nbf` (not before).

## How do different backend frameworks handle JWT clock skew?

Frameworks differ dramatically in how much clock skew leeway they permit by default. While languages like Go, Python, and Node.js enforce strict zero-leeway validation out of the box, enterprise frameworks like Spring Security and ASP.NET Core build in default buffers.

Here is how common backend environments compare when handling token expiration:

| Framework / Runtime | Default Clock Skew Leeway | Effective Lifetime of a 60-Minute Token |
| :--- | :--- | :--- | 
| Node.js (`jsonwebtoken`) | 0 seconds | 60 minutes |
| Go (`golang-jwt`) | 0 seconds | 60 minutes |
| Python (`PyJWT`) | 0 seconds | 60 minutes |
| Spring Security (Java) | 60 seconds | 61 minutes |
| ASP.NET Core (.NET) | 300 seconds (5 minutes) | 65 minutes |

This discrepancy creates real edge cases in polyglot environments. Suppose your auth provider is written in Node.js, while your downstream microservices are a mix of Go and ASP.NET Core. A token that expired two minutes ago will fail validation on the Go service, but pass without friction on the .NET service.

## How do you disable clock skew in ASP.NET Core?

You can disable clock skew in ASP.NET Core by setting the `ClockSkew` property to `TimeSpan.Zero` inside your authentication configuration. This instructs the underlying identity model to reject tokens the exact second their `exp` timestamp passes.

Here is how to configure it in your `Program.cs`:

```csharp
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateLifetime = true,
            ClockSkew = TimeSpan.Zero // Overrides the 5-minute default
        };
    });
```

If your architecture relies on short-lived access tokens—say, five to ten minutes—leaving the default five-minute clock skew enabled effectively extends your token lifetime by 50% to 100%. In scenarios where fast revocation matters, reducing or eliminating this window is an essential hardening step.

## Frequently Asked Questions

### What is clock skew in JWT authentication?
Clock skew is the time difference between the system clock of the authentication server that generated the JWT and the system clock of the API server validating it. Validation libraries allow a leeway window so valid requests are not rejected due to minor timing drifts.

### Does the JWT specification mandate a 5-minute clock skew?
No, RFC 7519 does not specify a mandatory duration. The specification merely states that implementations may provide a small leeway to account for clock drift, leaving the actual threshold up to individual library authors.

### Should I always set ClockSkew to zero in ASP.NET Core?
Not necessarily. If your systems run in multi-region environments or use external third-party identity providers where NTP drift can occur, setting clock skew to zero can cause intermittent validation rejections. A balanced middle ground is often 30 to 60 seconds.
