Stats
Actions
Tags
ASP.NET Core security: JWT/OAuth2/Cookie authentication, authorization policies, input validation, SQL injection prevention, CSRF protection, secrets management, CORS, HSTS, and NuGet vulnerability scanning.
How this skill is triggered — by the user, by Claude, or both
Slash command
/everything-claude-code:aspnetcore-securityThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Security patterns for production-grade ASP.NET Core applications.
Security patterns for production-grade ASP.NET Core applications.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = config["Jwt:Issuer"],
ValidateAudience = true,
ValidAudience = config["Jwt:Audience"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(config["Jwt:Key"]!))
};
});
builder.Services.AddAuthentication()
.AddOpenIdConnect("AzureAd", options =>
{
options.Authority = $"https://login.microsoftonline.com/{config["AzureAd:TenantId"]}/v2.0";
options.ClientId = config["AzureAd:ClientId"];
// Never hardcode ClientSecret - use User Secrets or Key Vault
options.ClientSecret = config["AzureAd:ClientSecret"];
options.ResponseType = OpenIdConnectResponseType.Code;
});
// ✅ Policy-based authorization
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin").RequireClaim("department", "IT"));
options.AddPolicy("OrderManager", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18)));
});
// ✅ Custom requirement handler
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
var birthDateClaim = context.User.FindFirst(ClaimTypes.DateOfBirth)?.Value;
if (birthDateClaim != null &&
DateOnly.TryParse(birthDateClaim, out var birthDate) &&
birthDate.AddYears(requirement.MinimumAge) <= DateOnly.FromDateTime(DateTime.UtcNow))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
// ✅ Apply to endpoints
[Authorize(Policy = "AdminOnly")]
public class AdminController : ControllerBase { }
orders.MapPost("/", CreateOrder).RequireAuthorization("OrderManager");
// ✅ FluentValidation at API boundary
public class CreateOrderValidator : AbstractValidator<CreateOrderRequest>
{
public CreateOrderValidator()
{
RuleFor(x => x.CustomerName)
.NotEmpty().WithMessage("Customer name is required")
.MaximumLength(200).WithMessage("Name too long")
.Matches(@"^[a-zA-Z\s]+$").WithMessage("Name contains invalid characters");
RuleFor(x => x.Amount)
.GreaterThan(0).LessThanOrEqualTo(10000);
}
}
// ✅ HtmlSanitizer for HTML content
using Ganss.Xss;
var sanitizer = new HtmlSanitizer();
var safeHtml = sanitizer.Sanitize(userSubmittedHtml);
// ❌ NEVER string concatenation in SQL
context.Database.ExecuteSqlRaw($"SELECT * FROM Orders WHERE Id = {id}");
// ✅ EF Core LINQ (always parameterized)
context.Orders.Where(o => o.Id == id).FirstOrDefaultAsync(ct);
// ✅ Parameterized raw SQL when needed
context.Orders.FromSqlRaw("SELECT * FROM Orders WHERE Id = {0}", id);
// Or with FormattableString (compile-time safe)
context.Orders.FromSqlInterpolated($"SELECT * FROM Orders WHERE Id = {id}");
// ✅ ASP.NET Core Identity PasswordHasher
var hasher = new PasswordHasher<ApplicationUser>();
var hashed = hasher.HashPassword(user, plainTextPassword);
var result = hasher.VerifyHashedPassword(user, hashed, inputPassword);
// ✅ Or use ASP.NET Core Identity directly (recommended)
await userManager.CreateAsync(user, password);
await signInManager.PasswordSignInAsync(email, password, isPersistent: false, false);
// ✅ Antiforgery for traditional MVC forms
builder.Services.AddAntiforgery(options =>
{
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.HeaderName = "X-CSRF-TOKEN";
});
// ✅ For SPAs: double-submit cookie pattern
app.Use(async (context, next) =>
{
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!,
new CookieOptions { HttpOnly = false });
await next(context);
});
// ❌ NEVER hardcode secrets
private const string ApiKey = "sk-abc123"; // DO NOT COMMIT
// ❌ NEVER in appsettings.json
// { "Stripe": { "SecretKey": "sk-live-..." } }
// ✅ Development: User Secrets
// dotnet user-secrets set "Stripe:SecretKey" "sk-test-..."
// Automatically loaded in Development environment
// ✅ Production: Environment variables
// STRIPE__SECRETKEY=sk-live-...
// ✅ Production: Azure Key Vault
builder.Configuration.AddAzureKeyVault(
new Uri($"https://{vaultName}.vault.azure.net/"),
new DefaultAzureCredential());
// ✅ HSTS
builder.Services.AddHsts(options =>
{
options.MaxAge = TimeSpan.FromDays(365);
options.IncludeSubDomains = true;
});
app.UseHsts();
// ✅ Content Security Policy
app.Use(async (context, next) =>
{
context.Response.Headers.Append("Content-Security-Policy",
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");
context.Response.Headers.Append("X-Content-Type-Options", "nosniff");
context.Response.Headers.Append("X-Frame-Options", "DENY");
await next();
});
// ✅ Restrictive CORS
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowFrontend", policy =>
{
policy.WithOrigins("https://myapp.com", "https://www.myapp.com")
.WithMethods("GET", "POST", "PUT", "DELETE")
.WithHeaders("Content-Type", "Authorization")
.AllowCredentials(); // Only with explicit origins
});
});
// ❌ NEVER: AllowAnyOrigin + AllowCredentials (CORS spec violation)
policy.AllowAnyOrigin().AllowCredentials(); // SECURITY RISK!
# ✅ Run in CI and before deployment
dotnet list package --vulnerable
dotnet list package --deprecated
dotnet list package --outdated
# ✅ Fail CI on vulnerabilities
dotnet list package --vulnerable --include-transitive | grep -c "has the following vulnerable" && exit 1 || exit 0
[Authorize] or explicit AllowAnonymousdotnet list package --vulnerable returns no critical issuesUseHttpsRedirection + UseHsts)PasswordHasher<T> or ASP.NET Core Identitynpx claudepluginhub jed1978/everything-claude-codeCreates bite-sized, testable implementation plans from specs or requirements, with file structure and task decomposition. Activates before coding multi-step tasks.