JSON Web Tokens (JWTs) are commonly used to authenticate requests to ASP.NET Core APIs. A token is signed by an issuer, and the API verifies that signature before accepting the request. The signing key protects the token from unauthorized changes.
The challenge starts when a signing key needs to change. You might need to rotate a key because of a security policy, a scheduled maintenance task, a suspected compromise, or a change in your key management system.
Replacing the old key immediately can break authentication. Tokens issued before the change may still be valid, but an API that knows only the new key cannot verify their signatures. Users can suddenly receive 401 Unauthorized responses even when their tokens have not expired.
A safer approach is to introduce the new key before using it to issue tokens, accept both keys during the transition, and remove the old key only after tokens signed with it are no longer valid.
Why JWT Signing Key Rotation Matters
A JWT usually contains a header, a payload, and a signature. The signature allows the receiving API to verify that the token was signed by a trusted issuer and has not been modified.
Consider an application that signs tokens with an RSA private key. Its APIs use the corresponding public key to validate those tokens.
If the application replaces that key pair without updating the APIs, signature validation fails. The APIs may reject every token signed with the previous key.
There are three common reasons to rotate signing keys:
Security: Reduce the risk associated with long-lived cryptographic keys.
Key management: Follow an organization's scheduled rotation policy.
Incident response: Replace a key that may have been exposed.
Rotation should not require every user to sign in again. Whether users need to authenticate again depends on the token lifetime, the available validation keys, and the reason for the rotation.
If a key is compromised, the response may need to be more aggressive. Continuing to trust a compromised key can allow an attacker to create valid tokens. In that situation, security may take priority over uninterrupted access.
Understand the Difference Between Signing and Validation
Before implementing rotation, separate the responsibilities of the token issuer and the API that validates tokens.
The issuer signs a token using its private key. The API verifies the signature using the corresponding public key.
With an asymmetric algorithm such as RS256, the private key does not need to be distributed to every API. This is useful when several services validate tokens issued by one authentication service.
During a normal rotation, the issuer should sign new tokens with the new private key. APIs should temporarily trust both the old and new public keys.
The JWT header can include a key ID, or kid, that identifies the key used to sign the token. A validator can use this identifier to select the appropriate key.
For example, a token header may contain:
{
"alg": "RS256",
"typ": "JWT",
"kid": "signing-key-2026-02"
}
The kid is an identifier, not a secret. It does not replace signature verification. An API must still validate the signature, issuer, audience, expiration, and other required claims.
Follow a Safe Key Rotation Sequence
The order of deployment is important. If the issuer starts using a new key before the APIs trust it, requests may fail.
A typical planned rotation has four stages.
Stage 1: Create the New Key
Generate a new signing key using your organization's approved key management process. Store the private key in a secure service, such as a managed key vault or hardware security module, when supported by your infrastructure.
Assign the new key a unique identifier.
Do not store private keys directly in source code, commit them to a repository, or include them in application logs.
Stage 2: Distribute the New Public Key
Update every API that validates the tokens so it trusts both the old and new public keys.
At this stage, the issuer still signs new tokens with the old key. The new key is trusted but not yet active for token issuance.
Deploy and verify this configuration across all API instances and services that validate these tokens.
Stage 3: Start Signing with the New Key
Once the validators have the new public key, change the issuer to use the new private key.
New tokens now contain the new key ID and use the new signing key. Existing tokens remain verifiable because the APIs still trust the old public key.
Stage 4: Retire the Old Key
Wait until tokens signed with the old key can no longer be valid. Account for the maximum token lifetime and the clock-skew allowance configured by the validators.
Then remove the old public key from normal validation configuration.
If refresh tokens or other credentials have separate lifetimes and validation rules, handle those independently. A JWT signing key rotation does not automatically rotate every credential used by an authentication system.
Configure Multiple Signing Keys in ASP.NET Core
ASP.NET Core applications commonly validate JWTs through the JWT bearer authentication handler. The following example shows how an API can accept tokens signed by either of two trusted RSA keys.
Assume your configuration or key management service has already provided the old and new public keys. The example focuses on validation rather than showing how to load or store private key material.
First, define a collection of trusted signing keys:
using Microsoft.IdentityModel.Tokens;
using System.Security.Cryptography;
var oldPublicKey = RSA.Create();
oldPublicKey.ImportFromPem(
File.ReadAllText("keys/old-public-key.pem"));
var newPublicKey = RSA.Create();
newPublicKey.ImportFromPem(
File.ReadAllText("keys/new-public-key.pem"));
var oldSigningKey = new RsaSecurityKey(oldPublicKey)
{
KeyId = "signing-key-2026-01"
};
var newSigningKey = new RsaSecurityKey(newPublicKey)
{
KeyId = "signing-key-2026-02"
};
var trustedSigningKeys = new SecurityKey[]
{
oldSigningKey,
newSigningKey
};
This illustrates the validation configuration. In production, load public keys from your approved configuration or key distribution mechanism, and manage the lifetime of cryptographic objects correctly. Do not deploy private keys to an API that only needs to validate tokens.
Next, configure JWT bearer authentication:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters =
new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "https://auth.example.internal",
ValidateAudience = true,
ValidAudience = "orders-api",
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = trustedSigningKeys,
ValidAlgorithms = new[]
{
SecurityAlgorithms.RsaSha256
}
};
});
Replace the example issuer and audience with the values used by your application. The configured keys must correspond to the issuer's actual signing keys.
The IssuerSigningKeys property allows the handler to validate tokens using any of the trusted keys. The token's key ID helps identify the appropriate key, but the API must still verify the cryptographic signature.
The ValidAlgorithms setting also limits accepted signing algorithms. Choose algorithms that match your token issuer and security requirements.
This example assumes that the keys are available when the application starts. If your system rotates keys dynamically, you need a key refresh mechanism or a configuration reload strategy that updates the validation key set safely. Restarting every API manually for every rotation is usually not a good long-term solution.
Sign New Tokens with the Active Key
The token issuer should use one active signing key when creating new tokens. Keep the old key available for validation, but do not continue signing with it after the planned switch.
For an RSA-based issuer, the core token creation code can look like this:
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using var privateKey = RSA.Create();
privateKey.ImportFromPem(
File.ReadAllText("keys/active-private-key.pem"));
var activeSigningKey = new RsaSecurityKey(privateKey)
{
KeyId = "signing-key-2026-02"
};
var credentials = new SigningCredentials(
activeSigningKey,
SecurityAlgorithms.RsaSha256);
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, "user-123"),
new Claim(ClaimTypes.Name, "sample-user")
};
var token = new JwtSecurityToken(
issuer: "https://auth.example.internal",
audience: "orders-api",
claims: claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddMinutes(15),
signingCredentials: credentials);
var tokenString = new JwtSecurityTokenHandler()
.WriteToken(token);
The private key file is shown only to make the example easier to follow. In production, retrieve or use the signing key through your approved secret management or cryptographic service. Avoid loading a private key from a file on every request; initialize the signing service appropriately and handle key access securely.
The short token lifetime in this example is illustrative. Select a lifetime based on your application's security requirements and refresh-token strategy.
Notice that the active signing key has a KeyId. The token handler uses that identifier in the JWT header, allowing validators to identify the key associated with the signature.
Calculate When the Old Key Can Be Removed
Do not remove an old validation key simply because the issuer has switched to the new one.
Suppose the issuer creates access tokens with a lifetime of 15 minutes. The final token signed with the old key could be issued just before the switch. That token may remain valid for another 15 minutes.
If the API allows a clock-skew tolerance, it may accept the token for a little longer. The old key should remain trusted until the maximum validity window has passed, including the configured clock skew.
A useful planning rule is:
Old-key retirement time = last old-key issuance time + maximum token lifetime + allowed clock skew.
This calculation assumes the tokens have the stated maximum lifetime and that the API validates expiration correctly. If the system permits longer-lived tokens, different token profiles, or other credentials, account for those as well.
Also consider whether tokens can be issued by multiple application instances. Before switching keys, make sure every issuer instance has stopped signing with the old key. Otherwise, an instance that has not received the update may continue creating old-key tokens.
Common Mistakes During Key Rotation
Replacing the key in one deployment
Changing the issuer and validator configuration at the same time can create a period in which they disagree about which key is trusted.
Deploy trust for the new key first, then switch signing.
Removing the old key too early
Tokens signed with the old key can remain valid after the issuer starts using the new key. Removing the old public key too soon causes valid tokens to fail signature validation.
Keep the old key available for the full transition window.
Assuming kid makes a token trustworthy
A key ID is metadata supplied in the token header. It does not prove that the token is legitimate.
Always validate the signature and required claims. Never accept a token merely because its kid matches a known identifier.
Sharing private keys with every API
For distributed systems, asymmetric signing allows the issuer to hold the private key while APIs receive only public keys. This reduces the number of systems that need access to sensitive signing material.
With a symmetric algorithm such as HS256, every service that validates signatures generally needs the shared secret. That arrangement requires careful control over secret distribution and access.
Ignoring key caching
An API may cache its validation configuration or public-key set. Updating the central key store does not guarantee that every running instance immediately sees the new key.
Use a predictable refresh process, monitor deployment status, and test how each service handles a new key.
How to Test a Rotation Before Production
Run a rotation test in a staging environment that uses the same validation configuration and deployment sequence as production.
Verify the following cases:
A token signed with the old key succeeds while the old public key remains trusted and the token has not expired.
A token signed with the new key succeeds after the validators receive the new public key.
A token signed with an unknown key fails validation.
A token with an invalid signature fails validation.
An expired token fails validation.
Tokens with the wrong issuer or audience fail validation.
Removing the old key causes old-key tokens to fail only after the planned retirement point.
Every API instance and authentication-dependent service receives the updated key configuration.
Monitor authentication failures during the change. A sudden increase in 401 Unauthorized responses may indicate that a validator has not received the new public key or that the issuer is still using an unexpected key.
For a planned rotation, keep a rollback procedure ready. If the new key causes validation problems, you may need to restore the previous active signing configuration while investigating. Do not roll back to a key that is known or suspected to be compromised.
Conclusion
JWT signing key rotation does not need to interrupt API access. The key is to separate the act of trusting a key from the act of using it to sign new tokens.
Create the new key, distribute its public key to validators, switch the issuer to the new private key, and retain the old public key until tokens signed with it have expired. After that, remove the old key from the trusted set.
For production ASP.NET Core systems, automate key distribution, keep private keys in secure storage, monitor authentication failures, and test the full rotation sequence before deploying it. These steps make routine key changes predictable while preserving the security checks that protect your APIs.
JWTJWT signing key rotationASP.NET CoreJWT Bearer authenticationRS256key ID (kid)token validation
Join the conversation! Your thoughts help the community grow.