Skip to content
dotnet-api-security logo

Dotnet API Security

dotnet-api-security

Secures ASP.NET Core APIs. Identity, OAuth/OIDC, JWT bearer, passkeys, CORS, rate limiting.

rudironsoni/Synaxis0installs2stars

SKILL.md

Full skill instructions

dotnet-api-security

API-level authentication, authorization, and security patterns for ASP.NET Core. This skill owns API auth implementation: ASP.NET Core Identity configuration, OAuth 2.0/​OIDC integration, JWT bearer token handling, passkey (WebAuthn) authentication, CORS policies, Content Security Policy headers, and rate limiting.

Scope

  • ASP.NET Core Identity configuration and Identity API endpoints
  • OAuth 2.0 / OpenID Connect integration with external providers
  • JWT bearer token authentication and policy-based authorization
  • Passkey / WebAuthn authentication (.NET 10)
  • CORS policies and Content Security Policy headers
  • Rate limiting middleware (fixed window, sliding window, token bucket, concurrency)

Out of scope

  • OWASP Top 10 mitigations and deprecated security patterns -- see [skill:dotnet-security-owasp]
  • Secrets management and secure configuration -- see [skill:dotnet-secrets-management]
  • Cryptographic algorithm selection -- see [skill:dotnet-cryptography]
  • Blazor auth UI components -- see [skill:dotnet-blazor-auth]

Cross-references: [skill:dotnet-security-owasp] for OWASP security principles, [skill:dotnet-secrets-management] for secrets handling, [skill:dotnet-cryptography] for cryptographic best practices.


ASP.NET Core Identity

ASP.NET Core Identity provides user management, password hashing, role-based authorization, and two-factor authentication out of the box. It is the recommended starting point for applications that manage their own user accounts.


builder.Services.AddIdentityApiEndpoints<ApplicationUser>(options =>
{
    // Password requirements
    options.Password.RequiredLength = 12;
    options.Password.RequireNonAlphanumeric = true;
    options.Password.RequireUppercase = true;
    options.Password.RequireLowercase = true;
    options.Password.RequireDigit = true;

    // Lockout
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
    options.Lockout.MaxFailedAccessAttempts = 5;
    options.Lockout.AllowedForNewUsers = true;

    // User
    options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();

var app = builder.Build();

app.MapIdentityApi<ApplicationUser>(); // Maps /​register, /​login, /​refresh, /​manage endpoints

```text

### Identity API Endpoints (.NET 8+)

`MapIdentityApi<TUser>()` provides pre-built token-based authentication endpoints for SPAs and mobile clients without
Razor UI:

| Endpoint        | Method   | Description                         |
| --------------- | -------- | ----------------------------------- |
| `/​register`     | POST     | Create a new user account           |
| `/​login`        | POST     | Authenticate and receive tokens     |
| `/​refresh`      | POST     | Refresh an expired access token     |
| `/​confirmEmail` | GET      | Confirm email address               |
| `/​manage/​info`  | GET/​POST | Get/​update user profile             |
| `/​manage/​2fa`   | POST     | Configure two-factor authentication |

---

## OAuth 2.0 / OpenID Connect

For applications that delegate authentication to an external identity provider (Entra ID, Auth0, Okta, Keycloak),
configure OIDC middleware.

```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 = OpenIdConnectResponseType.Code; // Authorization Code Flow
    options.SaveTokens = true;
    options.GetClaimsFromUserInfoEndpoint = true;

    options.Scope.Add("openid");
    options.Scope.Add("profile");
    options.Scope.Add("email");

    options.MapInboundClaims = false; // Preserve original claim types
    options.TokenValidationParameters.NameClaimType = "name";
    options.TokenValidationParameters.RoleClaimType = "roles";
});

```text

**Gotcha:** `MapInboundClaims = false` prevents the Microsoft OIDC handler from remapping standard JWT claims (e.g.,
`sub` to `http://schemas.xmlsoap.org/​ws/​2005/​05/​identity/​claims/​nameidentifier`). Set this to `false` to preserve the
original claim types from the identity provider.

---

## JWT Bearer Token Authentication

For API-only scenarios where the client sends a JWT in the `Authorization` header:

```csharp

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = builder.Configuration["Jwt:Authority"];
        options.Audience = builder.Configuration["Jwt:Audience"];

        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ClockSkew = TimeSpan.FromMinutes(1) // Default is 5 min; tighten for security
        };
    });

builder.Services.AddAuthorization();

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();

// Protect endpoints
app.MapGet("/​api/​profile", (ClaimsPrincipal user) =>
    TypedResults.Ok(new { Name = user.Identity?.Name }))
    .RequireAuthorization();

```text

### Policy-Based Authorization

```csharp

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("AdminOnly", policy =>
        policy.RequireRole("Admin"))
    .AddPolicy("PremiumUser", policy =>
        policy.RequireClaim("subscription", "premium"))
    .SetFallbackPolicy(new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build());

```text

---

## Passkeys / WebAuthn (.NET 10)

.NET 10 introduces built-in passkey (WebAuthn/​FIDO2) support for passwordless authentication. Passkeys use public-key
cryptography and are phishing-resistant.

```csharp

// .NET 10: Add passkey support to Identity
builder.Services.AddIdentityApiEndpoints<ApplicationUser>(options =>
{
    options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders()
.AddPasskeys(); // Enable WebAuthn passkey authentication

var app = builder.Build();

app.MapIdentityApi<ApplicationUser>();
// Passkey registration and authentication endpoints are added automatically

```text

### Passkey Registration Flow

1. Client calls `/​passkey/​register/​options` to get a `PublicKeyCredentialCreationOptions` challenge
2. Client creates a credential using the Web Authentication API (`navigator.credentials.create`)
3. Client sends the attestation response to `/​passkey/​register`
4. Server validates and stores the credential

### Passkey Authentication Flow

1. Client calls `/​passkey/​login/​options` to get a `PublicKeyCredentialRequestOptions` challenge
2. Client signs the challenge using `navigator.credentials.get`
3. Client sends the assertion response to `/​passkey/​login`
4. Server validates the assertion and issues a session/​token

**Key benefits:** No passwords to phish, no credentials stored server-side (only public keys), built-in resistance to
replay attacks.

---

## CORS Policies

Cross-Origin Resource Sharing (CORS) controls which origins can call your API. Always use explicit, named policies --
never use `AllowAnyOrigin()` in production.

```csharp

builder.Services.AddCors(options =>
{
    options.AddPolicy("Production", policy =>
    {
        policy.WithOrigins(
                "https://app.example.com",
                "https://admin.example.com")
            .WithMethods("GET", "POST", "PUT", "DELETE")
            .WithHeaders("Content-Type", "Authorization")
            .SetPreflightMaxAge(TimeSpan.FromMinutes(10)); // Cache preflight
    });

    options.AddPolicy("Development", policy =>
    {
        policy.WithOrigins("https://localhost:5173") // Vite dev server
            .AllowAnyMethod()
            .AllowAnyHeader()
            .AllowCredentials();
    });
});

var app = builder.Build();
app.UseCors(app.Environment.IsDevelopment() ? "Development" : "Production");

```text

### Common CORS Pitfalls

- **`AllowAnyOrigin()` + `AllowCredentials()`** is rejected at runtime by ASP.NET Core. But
  `SetIsOriginAllowed(_ => true)` + `AllowCredentials()` silently allows all origins -- never use this pattern.
- **Preflight caching:** Without `SetPreflightMaxAge`, browsers send an OPTIONS request before every cross-origin
  request. Set a reasonable cache duration (10-60 minutes) to reduce latency.
- **Wildcard headers with credentials:** `AllowAnyHeader()` combined with `AllowCredentials()` works in ASP.NET Core but
  may behave unexpectedly in some browsers. Prefer explicit header lists.
- **CORS middleware order:** `UseCors()` must be called after `UseRouting()` and before `UseAuthorization()`.

---

## Content Security Policy (CSP)

Content Security Policy headers prevent XSS, clickjacking, and other injection attacks by controlling which resources
the browser can load.

```csharp

app.Use(async (context, next) =>
{
    // API-focused CSP -- restrict all content sources
    context.Response.Headers.Append(
        "Content-Security-Policy",
        "default-src 'none'; frame-ancestors 'none'");

    // Additional security headers
    context.Response.Headers.Append("X-Content-Type-Options", "nosniff");
    context.Response.Headers.Append("X-Frame-Options", "DENY");
    context.Response.Headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");
    context.Response.Headers.Append("Permissions-Policy",
        "camera=(), microphone=(), geolocation=()");

    await next();
});

```text

For APIs serving HTML responses (Razor Pages, Blazor Server), use a more permissive CSP with nonces:

```csharp

app.Use(async (context, next) =>
{
    var nonce = Convert.ToBase64String(RandomNumberGenerator.GetBytes(16));
    context.Items["CspNonce"] = nonce;

    context.Response.Headers.Append(
        "Content-Security-Policy",
        $"default-src 'self'; script-src 'self' 'nonce-{nonce}'; style-src 'self' 'nonce-{nonce}'");

    await next();
});

```text

---

## Rate Limiting

ASP.NET Core includes built-in rate limiting middleware (`Microsoft.AspNetCore.RateLimiting`, .NET 7+). Four algorithms
are available: fixed window, sliding window, token bucket, and concurrency limiter.

### Fixed Window

```csharp

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("fixed", limiterOptions =>
    {
        limiterOptions.PermitLimit = 100;
        limiterOptions.Window = TimeSpan.FromMinutes(1);
        limiterOptions.QueueLimit = 0; // Reject immediately when limit reached
    });
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
});

var app = builder.Build();
app.UseRateLimiter();

app.MapGet("/​api/​products", GetProducts)
    .RequireRateLimiting("fixed");

```text

### Sliding Window

```csharp

builder.Services.AddRateLimiter(options =>
{
    options.AddSlidingWindowLimiter("sliding", limiterOptions =>
    {
        limiterOptions.PermitLimit = 100;
        limiterOptions.Window = TimeSpan.FromMinutes(1);
        limiterOptions.SegmentsPerWindow = 6; // 10-second segments
        limiterOptions.QueueLimit = 0;
    });
});

```text

### Token Bucket

```csharp

builder.Services.AddRateLimiter(options =>
{
    options.AddTokenBucketLimiter("token", limiterOptions =>
    {
        limiterOptions.TokenLimit = 100;
        limiterOptions.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
        limiterOptions.TokensPerPeriod = 10;
        limiterOptions.QueueLimit = 0;
    });
});

```text

### Concurrency Limiter

```csharp

builder.Services.AddRateLimiter(options =>
{
    options.AddConcurrencyLimiter("concurrent", limiterOptions =>
    {
        limiterOptions.PermitLimit = 10; // Max 10 concurrent requests
        limiterOptions.QueueLimit = 5;
        limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
    });
});

```text

### Per-User Rate Limiting

```csharp

builder.Services.AddRateLimiter(options =>
{
    options.AddPolicy("per-user", httpContext =>
    {
        var userId = httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier)
            ?? httpContext.Connection.RemoteIpAddress?.ToString()
            ?? "anonymous";

        return RateLimitPartition.GetFixedWindowLimiter(userId,
            _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 60,
                Window = TimeSpan.FromMinutes(1)
            });
    });
});

```text

**Gotcha:** `UseRateLimiter()` must be called after `UseRouting()` and before `UseAuthorization()` and endpoint mapping
to apply correctly.

---

## Agent Gotchas

1. **Do not use `AllowAnyOrigin()` in production CORS policies** -- always specify explicit origins. See
   [skill:dotnet-security-owasp] for CORS security implications.
2. **Do not forget `MapInboundClaims = false`** when using external OIDC providers -- without it, claim types are
   remapped to long XML namespace URIs, breaking role and name lookups.
3. **Do not hardcode JWT signing keys in source code or `appsettings.json`** -- use user secrets for development and
   environment variables or managed identity for production. See [skill:dotnet-secrets-management].
4. **Do not set `ClockSkew` to `TimeSpan.Zero`** -- small clock differences between token issuer and validator will
   cause spurious 401 errors. Use 1-2 minutes.
5. **Do not forget middleware order** -- `UseAuthentication()` must come before `UseAuthorization()`, and `UseCors()`
   must come before `UseAuthorization()`.
6. **Do not use `AllowAnyMethod()` and `AllowAnyHeader()` together in production** -- explicitly list allowed methods
   and headers to follow the principle of least privilege.
7. **Do not skip rate limiting on authentication endpoints** -- `/​login` and `/​register` are common brute-force targets.
   Apply rate limiting to prevent credential stuffing.
8. **Do not use exception-driven rejection in auth paths** -- use defensive parsing (`TryFromBase64String`, length
   validation) on attacker-controlled input instead.

---

## Prerequisites

- .NET 8.0+ (LTS baseline for Identity API endpoints, JWT bearer, CORS, rate limiting)
- .NET 10.0 for passkey/​WebAuthn support
- `Microsoft.AspNetCore.Authentication.JwtBearer` for JWT bearer authentication
- `Microsoft.AspNetCore.Authentication.OpenIdConnect` for OIDC integration
- `Microsoft.AspNetCore.RateLimiting` (included in shared framework .NET 7+)

---



## Code Navigation (Serena MCP)

**Primary approach:** Use Serena symbol operations for efficient code navigation:

1. **Find definitions**: `serena_find_symbol` instead of text search
2. **Understand structure**: `serena_get_symbols_overview` for file organization
3. **Track references**: `serena_find_referencing_symbols` for impact analysis
4. **Precise edits**: `serena_replace_symbol_body` for clean modifications

**When to use Serena vs traditional tools:**
- ✅ **Use Serena**: Navigation, refactoring, dependency analysis, precise edits
- ✅ **Use Read/​Grep**: Reading full files, pattern matching, simple text operations
- ✅ **Fallback**: If Serena unavailable, traditional tools work fine

**Example workflow:**
```text
# Instead of:
Read: src/​Services/​OrderService.cs
Grep: "public void ProcessOrder"

# Use:
serena_find_symbol: "OrderService/​ProcessOrder"
serena_get_symbols_overview: "src/​Services/​OrderService.cs"
```
## References

- [ASP.NET Core Security](https://learn.microsoft.com/en-us/aspnet/core/security/?view=aspnetcore-10.0)
- [ASP.NET Core Identity](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-10.0)
- [JWT Bearer Authentication](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/jwt-bearer?view=aspnetcore-10.0)
- [OAuth 2.0 / OIDC](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/?view=aspnetcore-10.0)
- [CORS in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-10.0)
- [Rate Limiting Middleware](https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit?view=aspnetcore-10.0)
- [WebAuthn/​Passkeys](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/passkeys?view=aspnetcore-10.0)

More skills from rudironsoni

dotnet-project-analysis logo
rudironsoni/Synaxis

dotnet-project-analysis

Analyzes .NET solution layout and build config -- .sln, .csproj, CPM.

2 0
View
wiki-agents-md logo
rudironsoni/Synaxis

wiki-agents-md

Generates AGENTS.md files for coding agent context

2 0
View
wiki-architect logo
rudironsoni/Synaxis

wiki-architect

Analyzes repos and generates structured catalogues

2 0
View

AutoFixture and NSubstitute Integration Guide - Implementing Auto-Mocking. Use when you need to automatically create mock objects and simplify testing of complex dependency injection. Covers AutoNSubstituteDataAttribute, Frozen mechanism, Greedy construction strategy. Includes customized handling...

2 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View