From dotnet Claude Kit
Implements authentication and authorization in ASP.NET Core: JWT bearer tokens, OpenID Connect, ASP.NET Identity, policy-based authorization, roles, claims, and API key auth.
How this skill is triggered — by the user, by Claude, or both
Slash command
/dotnet-claude-kit:authenticationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
1. **Use ASP.NET Identity for user management** — Don't build your own user store. Identity handles password hashing, lockout, two-factor, and email confirmation.
[Authorize(Roles = "Admin")].// Program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!)),
ClockSkew = TimeSpan.Zero
};
});
builder.Services.AddAuthorization();
public class TokenService(IConfiguration config, TimeProvider clock)
{
public string GenerateToken(User user, IEnumerable<string> roles)
{
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, user.Id),
new(ClaimTypes.Email, user.Email!),
new(ClaimTypes.Name, user.UserName!)
};
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Jwt:Key"]!));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: config["Jwt:Issuer"],
audience: config["Jwt:Audience"],
claims: claims,
expires: clock.GetUtcNow().AddHours(1).DateTime,
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
// Define policies
builder.Services.AddAuthorizationBuilder()
.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"))
.AddPolicy("CanManageOrders", policy => policy
.RequireAuthenticatedUser()
.RequireClaim("permission", "orders:write"))
.AddPolicy("MinimumAge", policy => policy
.AddRequirements(new MinimumAgeRequirement(18)));
// Custom requirement + handler
public class MinimumAgeRequirement(int minimumAge) : IAuthorizationRequirement
{
public int MinimumAge => minimumAge;
}
public class MinimumAgeHandler(TimeProvider clock) : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
var dateOfBirthClaim = context.User.FindFirst("date_of_birth");
if (dateOfBirthClaim is not null &&
DateOnly.TryParse(dateOfBirthClaim.Value, out var dob) &&
dob.AddYears(requirement.MinimumAge) <= DateOnly.FromDateTime(clock.GetUtcNow().DateTime))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
// Protect an entire group
app.MapGroup("/api/admin")
.WithTags("Admin")
.RequireAuthorization("AdminOnly")
.MapAdminEndpoints();
// Protect individual endpoints
group.MapPost("/", CreateOrder)
.RequireAuthorization("CanManageOrders");
// Allow anonymous on a protected group
group.MapGet("/public-info", GetPublicInfo)
.AllowAnonymous();
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = builder.Configuration["Oidc:Authority"];
options.ClientId = builder.Configuration["Oidc:ClientId"];
options.ClientSecret = builder.Configuration["Oidc:ClientSecret"];
options.ResponseType = "code";
options.SaveTokens = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
});
// In minimal API handlers — inject ClaimsPrincipal or HttpContext
group.MapGet("/me", (ClaimsPrincipal user) =>
{
var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
var email = user.FindFirstValue(ClaimTypes.Email);
return TypedResults.Ok(new { userId, email });
}).RequireAuthorization();
// BAD — magic strings, hard to refactor, not testable
[Authorize(Roles = "Admin,SuperAdmin,Manager")]
public class AdminController { }
// GOOD — policy-based
builder.Services.AddAuthorizationBuilder()
.AddPolicy("AdminAccess", p => p.RequireRole("Admin", "SuperAdmin", "Manager"));
group.MapGet("/", Handler).RequireAuthorization("AdminAccess");
// BAD — committed to source control
{
"Jwt": {
"Key": "super-secret-key-12345"
}
}
# GOOD — use user secrets in development
dotnet user-secrets set "Jwt:Key" "super-secret-key-12345"
// BAD — disabling validation
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false, // DON'T
ValidateAudience = false, // DON'T
ValidateLifetime = false, // DEFINITELY DON'T
};
// GOOD — validate everything (see JWT Bearer Authentication pattern above for full setup)
| Scenario | Recommendation |
|---|---|
| REST API | JWT Bearer authentication |
| Blazor Server / MVC | Cookie authentication |
| External identity provider | OpenID Connect |
| User registration / login | ASP.NET Identity |
| Permission checking | Policy-based authorization |
| Multi-tenant API | Claims-based with tenant claim |
| API-to-API communication | Client credentials (OAuth 2.0) |
| Simple API keys | Custom AuthenticationHandler<T> |
npx claudepluginhub codewithmukesh/dotnet-claude-kit --plugin dotnet-claude-kitConfigures JWT Bearer authentication for .NET APIs with access tokens, refresh tokens, token rotation, and user context extraction from claims.
Secures ASP.NET Core Web API endpoints with JWT Bearer token validation and Auth0 integration. Handles DPoP proof-of-possession binding.
Implementing API auth. Identity, OAuth/OIDC, JWT bearer, passkeys (WebAuthn), CORS, rate limiting.