Illustration unavailable: ASP.NET Core logo (original image source).
All code is illustrative and unexecuted.
Creating an illustrative signed token
For a real sign-in or delegated-access system, use an established OAuth/OIDC issuer. The code here illustrates a small closed-system JWT contract, not a complete standards-based authorization server. The original KeyStore Explorer link is preserved as a certificate-tool reference, not an instruction to create or install a production credential.
The issuing side needs an RSA private key accessible under an approved key-management policy. Pass it as an X509Certificate2 instance; the validator needs only the trusted public certificate. In a .NET project, System.IdentityModel.Tokens.Jwt supplies the token types below. Use compatible, pinned dependency versions.
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.IdentityModel.Tokens;
public static class ExampleTokenIssuer
{
// Closed-system illustration, not a replacement for an OAuth/OIDC server.
public static string Create(X509Certificate2 signingCertificate,
string issuer, string audience, string subject)
{
if (string.IsNullOrWhiteSpace(issuer) || string.IsNullOrWhiteSpace(audience)
|| string.IsNullOrWhiteSpace(subject))
throw new ArgumentException("Issuer, audience and subject are required.");
using RSA? rsa = signingCertificate.GetRSAPrivateKey();
if (rsa is null || rsa.KeySize < 2048)
throw new ArgumentException("An RSA private key of at least 2048 bits is required.");
var now = DateTime.UtcNow;
if (signingCertificate.NotBefore.ToUniversalTime() > now
|| signingCertificate.NotAfter.ToUniversalTime() <= now.AddMinutes(5))
throw new ArgumentException("Signing certificate does not cover the token lifetime.");
var descriptor = new SecurityTokenDescriptor
{
Issuer = issuer,
Audience = audience,
IssuedAt = now,
NotBefore = now,
Expires = now.AddMinutes(5),
Claims = new Dictionary<string, object>
{
[JwtRegisteredClaimNames.Sub] = subject,
[JwtRegisteredClaimNames.Jti] = Guid.NewGuid().ToString("N")
},
SigningCredentials = new SigningCredentials(
new X509SecurityKey(signingCertificate), SecurityAlgorithms.RsaSha256)
};
var handler = new JwtSecurityTokenHandler();
return handler.WriteToken(handler.CreateToken(descriptor));
}
}Issuer, audience and subject are explicit inputs, not optional placeholders. The function adds issued-at, not-before, expiration and token identifier values. A token identifier by itself does not prevent replay: an ordinary bearer token can be reused until it expires unless additional controls apply. Protect tokens in transit with HTTPS and never log them.
Enforcing JWT bearer authentication
This extension requires the trusted public certificate and fixed issuer/audience as arguments. Load them from administrator-controlled deployment configuration and fail startup if unavailable. Keep the certificate alive for the service lifetime. No request-supplied certificate, key path or issuer becomes trusted automatically.
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
public static class PinnedJwtAuthentication
{
public static IServiceCollection AddPinnedJwt(this IServiceCollection services,
X509Certificate2 trustedPublicCertificate, string issuer, string audience)
{
if (string.IsNullOrWhiteSpace(issuer) || string.IsNullOrWhiteSpace(audience))
throw new ArgumentException("Issuer and audience are required.");
using RSA? rsa = trustedPublicCertificate.GetRSAPublicKey();
if (rsa is null || rsa.KeySize < 2048)
throw new ArgumentException("Expected a trusted RSA public certificate.");
var key = new X509SecurityKey(trustedPublicCertificate);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.MapInboundClaims = false;
options.IncludeErrorDetails = false;
options.TokenValidationParameters = new TokenValidationParameters
{
RequireSignedTokens = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ValidAlgorithms = new[] { SecurityAlgorithms.RsaSha256 },
ValidateIssuer = true,
ValidIssuer = issuer,
ValidateAudience = true,
ValidAudience = audience,
ValidateLifetime = true,
RequireExpirationTime = true,
ClockSkew = TimeSpan.FromSeconds(30),
NameClaimType = "sub"
};
});
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser().Build();
});
return services;
}
}The original code set ValidateIssuer to true and then overwrote it with false. This version does not. It validates signature, an explicit algorithm allowlist, issuer, audience and expiry. Pinning the certificate is a deliberate trust model; plan certificate renewal, compromise removal and revocation policy separately. Name mapping is explicitly sub, rather than relying on unspecified defaults.
Protect endpoints, not just the middleware pipeline
A non-null User object is not proof of authentication. The fallback policy and explicit endpoint requirement below use RequireAuthenticatedUser. Business operations still need their own scope, role or resource-ownership checks; these examples grant no role merely from token presence.
// After AddPinnedJwt(...) has been called with approved deployment inputs:
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/private", () => "Authenticated endpoint").RequireAuthorization();The app variable is the existing WebApplication after service registration and build. The sample route is a local illustrative route, not an external URL. No tokens were issued and no key, certificate or server configuration was changed. The code was not compiled or executed.
Primary sources
- Microsoft JWT bearer configuration
- RFC 7519 JWT
- RFC 8725 JWT security practices
- Microsoft claims mapping
- Microsoft simple authorization
- KeyStore Explorer
Original English article: Configuring JWT Authentication with ASP.NET Core. Original publication date: 2021-08-11T16:52:00.009+03:00.