본문으로 건너뛰기
cccskills
무료GitHub에 공개

dotnet-api-security

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

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md16.2 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

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)

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Patterns and techniques for evaluating and improving AI agent outputs.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rudironsoni/Synaxis22026년 3월 17일 업데이트

Comprehensive AI prompt engineering safety review and improvement prompt. Analyzes prompts for safety, bias, security vulnerabilities, and effectiveness while providing detailed improvement recommendations.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rudironsoni/Synaxis22026년 3월 17일 업데이트

Use when user requests research requiring multiple sources, comprehensive analysis, or synthesis across topics - technical research, domain knowledge gathering, market analysis, or learning about complex subjects

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rudironsoni/Synaxis22026년 3월 17일 업데이트

deep-wiki

무료

AI-powered wiki generation for code repositories with commands, agents, and skills

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rudironsoni/Synaxis22026년 3월 17일 업데이트

Use when building .NET 10 or C# 14 applications; when using minimal APIs, modular monolith patterns, or feature folders; when implementing HTTP resilience, Options pattern, Channels, or validation; when seeing outdated patterns like old extension method syntax

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rudironsoni/Synaxis22026년 3월 17일 업데이트

Implements accessible .NET UI. SemanticProperties, ARIA, AutomationPeer, testing per platform.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rudironsoni/Synaxis22026년 3월 17일 업데이트

rudironsoni의 스킬 모두 보기

이 스킬의 문제 신고