JSON Web Tokens (JWTs) are commonly used to authenticate requests in ASP.NET Core APIs. A server signs a token with a secret key or a private key, and the API validates that signature when a client sends the token.
Over time, an application may need to change its signing key. The existing key might have been exposed, an employee may have left the organization, or the security policy may require regular key rotation. Sometimes, the team simply needs to replace an old cryptographic key with a stronger one.
The difficult part is that existing clients may still have valid JWTs signed with the old key. If the API switches to the new key immediately and rejects every token signed with the previous key, users may suddenly need to sign in again. Background services, mobile applications, and other systems can also experience authentication failures.
The solution is to plan key rotation so that the API can validate old tokens for an appropriate period while issuing new tokens with the new key. This article explains how JWT signing key rotation works, how to configure ASP.NET Core to support it, and how to handle security risks during the transition.
Why JWT Signing Keys Need to Be Rotated
A JWT usually contains three parts: a header, a payload, and a signature. The signature allows the receiving API to verify that the token has not been modified and was signed by a trusted key.
For example, a token header might contain the following information:
{
"alg": "RS256",
"typ": "JWT",
"kid": "signing-key-2026-02"
}
The alg field identifies the signing algorithm, while kid identifies the key used to sign the token. The kid value is useful when the application has multiple trusted signing keys.
A signing key may need to be replaced for several reasons:
- A key has been exposed or may have been compromised.
- The organization follows a key rotation policy.
- Cryptographic requirements have changed.
- An application is migrating to a different identity provider or key management system.
- A signing certificate is approaching expiration.
Routine rotation and emergency rotation should not be treated in the same way. During a planned rotation, the application can usually support both keys temporarily. If the old key is compromised, continuing to trust it may allow attackers to create tokens that the API accepts. In that situation, immediate revocation may be more important than preserving existing sessions.
Understand the Difference Between Signing and Validation Keys
JWT signing keys can be managed in different ways.
| Approach | Signing operation | Validation operation |
|---|
| HMAC, such as HS256 | Uses a shared secret | Uses the same shared secret |
| RSA, such as RS256 | Uses a private key | Uses the corresponding public key |
| ECDSA, such as ES256 | Uses a private key | Uses the corresponding public key |
With HMAC, every service that validates tokens needs access to the secret. This can make key distribution and rotation more difficult when many APIs share the same signing secret.
With asymmetric signing, the identity service keeps the private key and APIs receive the public key. The APIs can verify signatures without having the ability to create new valid tokens.
For systems with multiple APIs or independent services, asymmetric signing can simplify the separation of responsibilities. The correct choice depends on the architecture and security requirements.
How Safe JWT Key Rotation Works
A planned rotation usually follows a controlled sequence:
- Generate a new signing key.
- Distribute the new validation key to every API that must accept tokens signed with it.
- Confirm that the APIs can validate tokens using both the old and new keys.
- Start issuing new tokens with the new signing key.
- Allow tokens signed with the old key to expire, provided the old key remains trusted and there is no security incident.
- Remove the old validation key after the last legitimate token signed with it can no longer be accepted.
The order is important. If the issuer starts using the new key before the APIs know how to validate it, requests can fail.
Similarly, if the API removes the old key while clients still hold unexpired tokens signed with it, those clients will receive authentication failures.
Step 1: Create a Signing Key with a Key Identifier
For an RSA-based implementation, the signing service can load a private key from a certificate or an appropriate key management service. The API receives the corresponding public key.
The following example demonstrates how to create an RSA signing key in .NET for illustration. It does not persist the key, so it should not be used as a production key-generation and storage strategy.
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
using RSA rsa = RSA.Create(3072);
var signingKey = new RsaSecurityKey(rsa)
{
KeyId = "signing-key-2026-02"
};
var credentials = new SigningCredentials(
signingKey,
SecurityAlgorithms.RsaSha256);
RsaSecurityKey wraps the RSA key for use by the token-handling library. The KeyId property identifies the key, and SigningCredentials specifies that the token should be signed using RS256.
In production, create and store the private key through a suitable certificate store, hardware security module, or managed key service. Do not generate a new signing key each time the application starts. Doing so can invalidate tokens after restarts and can cause different application instances to issue tokens with incompatible keys.
The issuer should include the key identifier in the JWT header. This allows validation infrastructure to identify the appropriate trusted key.
Step 2: Configure ASP.NET Core to Validate Tokens
ASP.NET Core commonly uses the JWT bearer authentication handler to validate incoming access tokens.
A simplified configuration using an RSA public key looks like this:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Security.Cryptography;
var builder = WebApplication.CreateBuilder(args);
RSA publicRsa = RSA.Create();
// Load the trusted public key from a certificate
// or your configured key management source.
var validationKey = new RsaSecurityKey(publicRsa)
{
KeyId = "signing-key-2026-01"
};
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters =
new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "https://identity.example.internal",
ValidateAudience = true,
ValidAudience = "orders-api",
ValidateIssuerSigningKey = true,
IssuerSigningKey = validationKey,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(1),
ValidAlgorithms = new[]
{
SecurityAlgorithms.RsaSha256
}
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
The key-loading section is intentionally abbreviated. The application must load the actual trusted public key before configuring validation.
The configuration validates the issuer, audience, signature, expiration, and permitted signing algorithm. These checks should remain enabled during key rotation.
The issuer and audience values must match the identity provider and API configuration. They should not be weakened just to make a new key work.
Step 3: Allow Both Keys During the Rotation
During a planned rotation, the API needs to trust both the previous key and the new key.
One straightforward approach is to configure multiple trusted signing keys:
using Microsoft.IdentityModel.Tokens;
var oldKey = LoadTrustedPublicKey(
"signing-key-2026-01");
var newKey = LoadTrustedPublicKey(
"signing-key-2026-02");
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "https://identity.example.internal",
ValidateAudience = true,
ValidAudience = "orders-api",
ValidateIssuerSigningKey = true,
IssuerSigningKeys = new[]
{
oldKey,
newKey
},
ValidateLifetime = true,
ValidAlgorithms = new[]
{
SecurityAlgorithms.RsaSha256
}
};
LoadTrustedPublicKey() represents application-specific code that loads a public key from a trusted source. It is not a built-in .NET method.
The important change is IssuerSigningKeys. The validation handler can use the configured keys to validate tokens signed by either trusted key, subject to the other validation requirements.
This configuration is appropriate for a simple rotation, but a large system may use an identity provider's OpenID Connect metadata and JWKS endpoint instead. In that case, the provider publishes its public signing keys, and the validation library can refresh its trusted key information according to its configuration.
Do not blindly trust a kid supplied by the token. A key identifier is a lookup hint, not proof that a key is trustworthy. The API must resolve it against an approved key set.
Step 4: Start Issuing Tokens with the New Key
After every relevant API can validate the new public key, change the token issuer to sign new JWTs with the new private key.
For example:
var newSigningKey = LoadPrivateSigningKey(
"signing-key-2026-02");
var credentials = new SigningCredentials(
newSigningKey,
SecurityAlgorithms.RsaSha256);
// Pass credentials to the configured token handler
// when creating new JWT access tokens.
LoadPrivateSigningKey() is a placeholder for your key management implementation.
At this point, new tokens should carry the new key identifier, while tokens issued before the change continue to use the old identifier.
For applications running multiple instances, make sure every issuer uses the intended active key. A rolling deployment can otherwise result in different instances issuing tokens with different keys unexpectedly.
It is also useful to monitor token issuance and validation so that the team can confirm that new tokens use the expected key and old-key usage decreases over time.
Step 5: Remove the Old Key at the Right Time
The old public key should remain trusted only as long as necessary.
For planned rotation, determine the maximum remaining lifetime of tokens signed with the old key. Include any permitted clock skew and the actual token expiration policy in that calculation.
For example, if the longest-lived old access token expires after 30 minutes, the API may need to retain the old validation key for at least that remaining period after the last old-key token is issued, plus the configured clock-skew allowance.
Refresh tokens require separate consideration. Refresh tokens are often opaque values or server-side records rather than JWTs, and their rotation and revocation policies depend on the identity system. If a refresh operation issues a new access token, the issuer should use the currently active signing key.
When the old tokens are no longer valid, remove the old key from the trusted validation set and verify that all services have received the updated configuration.
If the old key is compromised, do not keep trusting it merely to avoid logging users out. Remove it from validation as part of the incident response, revoke affected sessions or refresh tokens where necessary, and assess whether tokens may have been forged.
Common Mistakes to Avoid
| Mistake | Why it causes problems | Better approach |
|---|
| Switching keys everywhere at once | Existing tokens may stop working | Coordinate validation and issuance changes |
| Removing the old key too early | Valid clients may receive authentication errors | Wait until legitimate old-key tokens expire |
Trusting any key identified by kid | An attacker may influence key selection | Resolve key IDs only against trusted key sources |
| Disabling signature validation | Tokens could be accepted without valid signatures | Keep signature validation enabled |
| Ignoring issuer and audience checks | Tokens intended for another service might be accepted | Validate both claims |
| Generating a new key on every restart | Tokens may become invalid unexpectedly | Use stable, securely managed keys |
| Keeping a compromised key active | Attackers may continue to forge accepted tokens | Revoke the key and follow incident-response procedures |
Troubleshooting JWT Key Rotation
New tokens return 401 Unauthorized
Check the JWT header's kid, the signing algorithm, and the public keys configured in the API. Confirm that the API trusts the public key corresponding to the private key used by the issuer.
Also check the issuer, audience, expiration, and system clock. A correct signature does not compensate for failed validation of other claims.
Old tokens stop working immediately
Verify whether the old key was removed from IssuerSigningKeys or from the identity provider's published key set. If the old key remains trusted, check whether the token has expired or whether its issuer, audience, or signature is invalid.
Only some application instances fail
Check whether all instances have received the same key configuration. During a rolling deployment, one instance may have the new key while another still has the previous configuration.
The identity provider uses JWKS
Check the provider's published signing keys, key identifiers, metadata refresh behavior, and any caching between the issuer and the API. A new key may be published before it is used, allowing validation services to discover it before the issuer switches to it.
Best Practices for Production
- Keep private signing keys separate from API validation services whenever asymmetric signing is appropriate.
- Use a managed key store or certificate management system rather than placing private keys in source control.
- Publish or distribute the new public key before issuing tokens signed with it.
- Keep both keys trusted during a planned transition, but only for a defined period.
- Validate issuer, audience, signature, lifetime, and an explicit set of permitted algorithms.
- Monitor which key identifiers are being used for issuance and validation.
- Test rotation in a staging environment, including rolling deployments and expired tokens.
- Document an emergency key-revocation procedure that does not depend on routine token expiration.
- Rotate refresh tokens and revoke sessions according to the identity provider's separate policies.
- Never log access tokens, private keys, or sensitive authentication material while troubleshooting.
Advantages and Disadvantages of Planned Key Rotation
Advantages
- Limits the period during which an old key remains trusted.
- Allows legitimate clients to continue using unexpired tokens during a planned transition.
- Supports key management policies and cryptographic maintenance.
- Makes it possible to replace signing keys without forcing every client to reauthenticate immediately.
Disadvantages
- Requires coordination between token issuers and validating APIs.
- Adds temporary complexity because multiple keys must be trusted.
- Can cause authentication failures if key distribution or configuration refresh is delayed.
- Requires a separate emergency procedure when a key is compromised.
Summary
Changing JWT signing keys in ASP.NET Core requires coordination between the service that issues tokens and the APIs that validate them. For planned rotation, distribute the new validation key first, begin signing new tokens with it, and retain the old validation key only until legitimate old tokens can no longer be accepted.
Use key identifiers to help select trusted keys, keep signature and claim validation enabled, and manage private keys securely. If a key is compromised, revoke it promptly instead of preserving trust for the sake of existing sessions.
A clear rotation process, combined with monitoring and testing, helps maintain API availability while keeping authentication secure.
Join the conversation! Your thoughts help the community grow.