JSON Web Tokens (JWTs) are widely used to authenticate requests in ASP.NET Core APIs. When a client signs in, the application may issue a token that the client sends with later requests. The API checks the token's signature to confirm that it was issued by a trusted authority and has not been changed.
But what happens when you need to replace the signing key?
You might need to rotate a key because it is approaching its planned expiration, your security policy requires regular rotation, or you suspect that the existing key has been exposed.
Changing the key without a proper plan can cause existing tokens to fail validation. Users may suddenly receive 401 Unauthorized responses even though their tokens have not expired.
The solution is to introduce the new key without immediately removing the old one. During the transition, the API accepts tokens signed with both keys, while the token issuer starts signing new tokens with the new key.
In this article, we will look at how JWT key rotation works in ASP.NET Core, how to configure multiple signing keys, and how to handle the transition safely.
Why JWT Signing Key Rotation Matters
A JWT commonly contains three parts:
Header: Identifies the token type and signing algorithm.
Payload: Contains claims such as the subject, roles, and expiration time.
Signature: Helps the API verify that the token has not been modified.
The signing key is used to create or verify the signature, depending on the signing algorithm.
For example, an API might issue a token using an HMAC signing key. If the application replaces that key immediately, tokens signed with the previous key will fail validation unless the old key remains available for verification.
This can affect web applications, mobile apps, background services, and third-party clients that still hold valid tokens.
Key rotation reduces the time a particular key remains in use. However, it must be planned around token lifetimes, issuer configuration, and the systems that validate tokens.
How to Rotate JWT Keys Safely
A safe rotation normally follows four stages:
Create a new signing key and keep it secret.
Configure token validators to accept both the old and new keys.
Start issuing new tokens using the new key.
Remove the old key after tokens signed with it can no longer be valid.
The order is important. If you switch the issuer to the new key before validators know about it, clients may receive tokens that the API rejects.
Likewise, removing the old key too early can invalidate tokens that clients are still using.
Step 1: Understand the Key ID (kid)
A JWT can include a kid value in its header. This value identifies which signing key was used to create the token.
For example, a token header might contain:
{
"alg": "HS256",
"typ": "JWT",
"kid": "key-2026-b"
}
Here, key-2026-b identifies the signing key.
When multiple keys are available, the validator can use the key ID to find the correct key for signature verification.
The kid value is not a secret. It is an identifier, not proof that a token is valid. The API must still verify the signature, issuer, audience, expiration, and other required claims.
Use unique key IDs and maintain a clear mapping between each ID and its corresponding key.
Step 2: Store Signing Keys Securely
Never hardcode production signing keys in source code or commit them to a repository.
For production applications, store keys in an appropriate secret-management system. Access should be restricted to the services that need to issue or validate tokens.
For example, an application might load its signing keys from a secret provider and map them into configuration.
{
"Jwt": {
"Issuer": "https://auth.example.com",
"Audience": "orders-api",
"ActiveKeyId": "key-2026-b"
}
}
This configuration contains only non-secret settings. The actual key material should be stored separately in a secure secret store.
For a local development environment, use a development-only secret mechanism. Do not use the same signing key across development, testing, and production.
Step 3: Issue New Tokens with the New Key
The token issuer should sign new tokens using the active key.
The following example shows the core token-generation logic for an HMAC-based JWT:
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
public static string CreateToken(
string userId,
string issuer,
string audience,
string keyId,
string signingSecret)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userId),
new Claim(JwtRegisteredClaimNames.Jti,
Guid.NewGuid().ToString())
};
var securityKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(signingSecret))
{
KeyId = keyId
};
var credentials = new SigningCredentials(
securityKey,
SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: issuer,
audience: audience,
claims: claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddMinutes(15),
signingCredentials: credentials);
return new JwtSecurityTokenHandler()
.WriteToken(token);
}
The KeyId property sets the key identifier used in the JWT header.
The example uses a short-lived access token to limit the period during which a stolen token can be reused. The appropriate lifetime depends on your application's security requirements.
The signing secret must be generated securely and have sufficient entropy. Avoid manually creating predictable secrets or using ordinary passwords as signing keys.
In a production implementation, load the active key from your secret-management system rather than passing raw secret values around unnecessarily.
Step 4: Allow the API to Validate Old and New Keys
The API must continue accepting valid tokens signed with the old key while new tokens are issued using the new key.
ASP.NET Core supports JWT bearer authentication through AddJwtBearer.
The following example shows the basic validation setup using two known signing keys:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
string issuer =
builder.Configuration["Jwt:Issuer"]!;
string audience =
builder.Configuration["Jwt:Audience"]!;
// Load these values from a secure secret provider.
string oldSecret = LoadOldSigningSecret();
string newSecret = LoadNewSigningSecret();
var oldKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(oldSecret))
{
KeyId = "key-2026-a"
};
var newKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(newSecret))
{
KeyId = "key-2026-b"
};
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters =
new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = issuer,
ValidateAudience = true,
ValidAudience = audience,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = new[]
{
oldKey,
newKey
},
ClockSkew = TimeSpan.FromMinutes(1)
};
});
builder.Services.AddAuthorization();
builder.Services.AddControllers();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
LoadOldSigningSecret() and LoadNewSigningSecret() are placeholders for your secret-provider integration. They are not built-in .NET methods.
This configuration allows the validator to check tokens against the configured signing keys. The token's kid can help identify the correct key, but the signature must still be verified.
Keep issuer and audience validation enabled. Do not disable signature validation or accept arbitrary keys simply to make a rotation succeed.
For a distributed application, every API instance must receive the updated validation configuration before the issuer starts using the new key.
Step 5: Follow a Safe Rotation Sequence
For a planned rotation, use the following process.
Phase 1: Prepare the New Key
Generate the new key and store it securely. Assign a new, unique key ID.
Do not remove the old key at this stage.
Phase 2: Update All Validators
Deploy the new validation configuration to every API instance, gateway, and service that validates these tokens.
Verify that the deployment has completed successfully before moving forward.
Phase 3: Switch Token Issuance
Change the issuer to sign new tokens with the new key ID and key material.
Existing tokens remain valid because the old verification key is still available.
Phase 4: Wait for Old Tokens to Expire
Determine the maximum lifetime of tokens signed with the old key. Include any permitted clock skew and deployment delays in your calculations.
If refresh tokens are used, understand that they are a separate credential mechanism. Rotating an access-token signing key does not automatically revoke or rotate refresh tokens.
Phase 5: Remove the Old Key
After old access tokens can no longer be valid, remove the old key from validation configuration.
Monitor authentication failures during and after the rollout. Keep a documented recovery procedure in case the new configuration causes unexpected problems.
What If the Signing Key Is Compromised?
A planned rotation and an emergency rotation are different situations.
If a signing key is exposed, continuing to accept tokens signed with it may allow an attacker to create forged tokens.
In that situation, prioritize containment. Stop trusting the compromised key, deploy the replacement key, invalidate affected sessions or tokens where possible, and investigate the exposure.
Some existing clients may need to authenticate again. That disruption can be necessary to protect the application.
Do not keep a compromised key active simply to avoid returning 401 Unauthorized responses.
Common Mistakes to Avoid
Removing the old key too early: Existing valid tokens can fail validation.
Switching the issuer before updating validators: Newly issued tokens may be rejected.
Using the same key ID for different keys: This makes key identification and troubleshooting harder.
Disabling signature validation: This can allow forged tokens to be accepted.
Ignoring token expiration: Old keys should remain available only as long as required.
Hardcoding secrets: Keys can leak through source control, logs, or build artifacts.
Updating only one API instance: Other instances may reject tokens issued by the new configuration.
Best Practices for Production Applications
Use a secret manager or an appropriate key-management service.
Keep token lifetimes as short as your application requirements allow.
Give each signing key a unique identifier.
Deploy verification keys before switching token issuance.
Validate the issuer, audience, signature, and token lifetime.
Test the complete rotation sequence in a staging environment.
Monitor authentication failures and unexpected changes in token validation.
Document planned and emergency rotation procedures.
For larger systems, consider asymmetric signing with a controlled private-key issuer and public-key validators. This reduces the need to distribute signing secrets to every API.
Automate rotation carefully, including deployment checks and removal of expired keys.
Advantages and Disadvantages of JWT Key Rotation
Advantages
Limits how long a signing key remains active.
Supports planned security maintenance.
Allows existing tokens to remain valid during a controlled transition.
Improves key-management practices across distributed applications.
Disadvantages
Requires coordination between token issuers and validators.
Adds configuration and deployment complexity.
Incorrect timing can cause authentication failures.
Emergency rotation may require users to sign in again.
Secret storage and synchronization become more important as the number of services grows.
Summary
Changing JWT signing keys in ASP.NET Core does not have to break existing API clients. The important step is to separate the process of accepting a key from the process of issuing new tokens.
First, configure all validators to trust the new key while retaining the old verification key. Next, switch the issuer to the new key. Finally, remove the old key after tokens signed with it can no longer be valid.
For a planned rotation, this approach allows clients to continue using existing valid access tokens. If a key is compromised, however, security must take priority over uninterrupted access.
A well-designed rotation process, secure key storage, strict token validation, and proper monitoring make JWT authentication easier to maintain and safer to operate.
Join the conversation! Your thoughts help the community grow.