authentication · codewithmukesh — Claude Code skill: JWT, OIDC and policies in ASP.NET
A skill that feeds your coding agent working patterns for ASP.NET Core auth: JWT bearer, OpenID Connect, Identity, authorization policies.
npx skills add codewithmukesh/dotnet-claude-kit --skill authentication -a claude-codeWhat authentication does
The skill puts ready-made ASP.NET Core patterns into your agent's context: JWT bearer setup, token generation, authorization policies, OpenID Connect, and reading the current user. The agent loads it on its own when the conversation mentions "auth", "JWT", "bearer token", "OIDC", "claims", "roles", or "RequireAuthorization".
Inside are four core principles plus code patterns. For user management the skill points to ASP.NET Identity rather than a hand-rolled store, since Identity already handles password hashing, lockout, two-factor, and email confirmation. APIs get bearer tokens, Blazor and MVC apps get cookies. Token generation runs through JsonWebTokenHandler from Microsoft.IdentityModel.JsonWebTokens; JwtSecurityTokenHandler is called the legacy stack.
There is a section on anti-patterns too. Role strings like [Authorize(Roles = "Admin,SuperAdmin,Manager")] are flagged as hard to refactor and test, so policies are the recommended route. Secrets in appsettings.json land on the bad list as well — dotnet user-secrets for development, environment variables in production. And do not turn off token validation: ValidateLifetime = false is marked "DEFINITELY DON'T" in the examples. If your project already has a working auth scheme, expect this skill to push against it, because it steers toward policies and Identity.
What it helps with
- wire up JWT bearer auth in ASP.NET Core without breaking token validation
- protect a group of endpoints with one policy instead of role strings
- generate a token carrying roles and claims with the current handler
- connect an external identity provider over OpenID Connect
- move the JWT signing key out of
appsettings.jsoninto user secrets
Example prompts for the agent
- Help me set up JWT bearer authentication in Program.cs
- Add a CanManageOrders policy that requires the permission claim
- Rewrite [Authorize(Roles = "Admin")] as an authorization policy
How to install authentication
1Skills CLI (Vercel)
npx skills add codewithmukesh/dotnet-claude-kit --skill authentication -a claude-code2Manually: put the files in place
~/.claude/skills/authentication/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/authentication/
1Skills CLI (Vercel)
npx skills add codewithmukesh/dotnet-claude-kit --skill authentication -a codex2Manually: put the files in place
.agents/skills/authentication/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add codewithmukesh/dotnet-claude-kit --skill authentication -a cursor2Manually: put the files in place
.cursor/skills/authentication/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install codewithmukesh/dotnet-claude-kit authentication2Manually: put the files in place
.github/skills/authentication/SKILL.md1Skills CLI (Vercel)
npx skills add codewithmukesh/dotnet-claude-kit --skill authentication -a gemini-cli2Platform CLI
gemini skills install https://github.com/codewithmukesh/dotnet-claude-kit --path skills/authenticationManually: put the files in place
.devin/skills/authentication/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add codewithmukesh/dotnet-claude-kit --skill authentication -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about authentication
How do I install authentication in Claude?
Run npx skills add codewithmukesh/dotnet-claude-kit --skill authentication -a claude-code. The skill lands in ~/.claude/skills/authentication/SKILL.md.
What does the authentication skill do?
It gives the agent ASP.NET Core auth patterns: JWT bearer, OpenID Connect, ASP.NET Identity, policies, role and claim checks, and API key authentication.
When does the agent load this skill?
On its own, whenever the task involves login, protecting endpoints, authorization rules, or mentions "auth", "JWT", "OIDC", "claims", "roles", "RequireAuthorization".
Source text
SKILL.md · MIT
---
name: authentication
description: >
Authentication and authorization for ASP.NET Core. Covers JWT bearer tokens,
OpenID Connect, ASP.NET Identity, authorization policies, role and claim-based
authorization, and API key authentication.
Load this skill when implementing login, protecting endpoints, designing
authorization rules, or when the user mentions "auth", "JWT", "bearer token",
"OIDC", "OpenID Connect", "Identity", "claims", "roles", "authorize",
"RequireAuthorization", "API key", or "cookie auth".
---
# Authentication & Authorization
## Core Principles
1. **Use ASP.NET Identity for user management** — Don't build your own user store. Identity handles password hashing, lockout, two-factor, email confirmation, and (since .NET 10) built-in passkey/WebAuthn support for passwordless login.
2. **JWT for APIs, cookies for web apps** — APIs use Bearer token authentication; Blazor/MVC apps use cookie authentication.
3. **Policy-based authorization over roles** — Policies are testable, composable, and more expressive than `[Authorize(Roles = "Admin")]`.
4. **Never store secrets in code** — Use user secrets in development, Azure Key Vault / environment variables in production.
## Patterns
### JWT Bearer Authentication
```csharp
// 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();
```
### Token Generation
Use `JsonWebTokenHandler` from `Microsoft.IdentityModel.JsonWebTokens` — it is the maintained, span-based handler that ASP.NET Core itself validates with. `JwtSecurityTokenHandler` (System.IdentityModel.Tokens.Jwt) is the legacy stack.
```csharp
public sealed class TokenService(IConfiguration config, TimeProvider clock)
{
private static readonly JsonWebTokenHandler TokenHandler = new();
public string GenerateToken(User user, IEnumerable<string> roles)
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Jwt:Key"]!));
var now = clock.GetUtcNow();
var descriptor = new SecurityTokenDescriptor
{
Issuer = config["Jwt:Issuer"],
Audience = config["Jwt:Audience"],
IssuedAt = now.UtcDateTime,
Expires = now.AddHours(1).UtcDateTime,
Claims = new Dictionary<string, object>
{
[JwtRegisteredClaimNames.Sub] = user.Id,
[JwtRegisteredClaimNames.Email] = user.Email!,
[JwtRegisteredClaimNames.Name] = user.UserName!,
["roles"] = roles.ToArray()
},
SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
};
return TokenHandler.CreateToken(descriptor);
}
}
```
### Policy-Based Authorization
```csharp
// 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;
}
}
```
### Protecting Endpoints
```csharp
// 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();
```
### OpenID Connect (External Identity Provider)
```csharp
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");
});
```
### Accessing Current User
```csharp
// 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();
```
## Anti-patterns
### Don't Use Role Strings Everywhere
```csharp
// 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");
```
### Don't Store Secrets in appsettings.json
```json
// BAD — committed to source control
{
"Jwt": {
"Key": "super-secret-key-12345"
}
}
```
```bash
# GOOD — use user secrets in development
dotnet user-secrets set "Jwt:Key" "super-secret-key-12345"
```
### Don't Skip Token Validation
```csharp
// 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)
```
## Decision Guide
| Scenario | Recommendation |
|----------|---------------|
| REST API | JWT Bearer authentication |
| Blazor Server / MVC | Cookie authentication |
| External identity provider | OpenID Connect |
| User registration / login | ASP.NET Identity |
| Passwordless login | ASP.NET Identity passkeys (WebAuthn, built-in since .NET 10) |
| 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>` |
© codewithmukesh · MIT · codewithmukesh/dotnet-claude-kit
Similar skills
Everything for Backend and APIs →authentication by other authors comes first.