dotnet-api-security について
ASP.NET Core 開発者向けの API セキュリティスキルです。認証・認可や各種防御設定を扱い、実装上の注意点も示します。
主な機能
- 01Identity と認証エンドポイントの設定
- 02OAuth 2.0、OIDC、JWT の統合
- 03Passkey / WebAuthn 認証の実装
- 04CORS と CSP ポリシーの設定
- 05リクエストのレート制限
活用シーン
- 01API にアカウント登録とログインを追加
- 02外部 ID プロバイダーとの接続
- 03API のクロスオリジンとコンテンツセキュリティポリシーの設定
- 04ログインなどのエンドポイントのリクエスト頻度を制限
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-api-securityインストール後、Claude Code で/dotnet-api-securityを入力して読み込まれたか確認します。
詳細
dotnet-api-security
ASP.NET Core 向けの API レベルの認証、認可、セキュリティパターン。このスキルは、API 認証の実装(ASP.NET Core Identity の構成、OAuth 2.0/OIDC 統合、JWT ベアラートークンの処理、パスキー(WebAuthn)認証、CORS ポリシー、Content Security Policy ヘッダー、レート制限)を担当します。
認証の担当範囲: このスキルは、API レベルの認証パターンを担当します。Blazor 固有の認証 UI(AuthorizeView、CascadingAuthenticationState、クライアント側のトークン処理)については、準備ができ次第 [skill:dotnet-blazor-auth] を参照してください。OWASP セキュリティ原則(分野横断的な脆弱性対策)については、[skill:dotnet-security-owasp] を参照してください。
対象外: OWASP Top 10 の緩和策と非推奨のセキュリティパターンについては、[skill:dotnet-security-owasp] を参照してください。シークレット管理とセキュアな構成については、[skill:dotnet-secrets-management] を参照してください。暗号アルゴリズムの選択については、[skill:dotnet-cryptography] を参照してください。Blazor 認証 UI コンポーネントについては、[skill:dotnet-blazor-auth] を参照してください。
相互参照:OWASP セキュリティ原則については [skill:dotnet-security-owasp]、シークレットの取り扱いについては [skill:dotnet-secrets-management]、暗号化のベストプラクティスについては [skill:dotnet-cryptography] を参照してください。
ASP.NET Core Identity
ASP.NET Core Identity は、ユーザー管理、パスワードのハッシュ化、ロールベースの認可、二要素認証を標準で提供します。独自のユーザーアカウントを管理するアプリケーションでは、推奨される出発点です。
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
Identity API エンドポイント(.NET 8 以降)
MapIdentityApi<TUser>() は、Razor UI を使わずに、SPA やモバイルクライアント向けのトークンベース認証エンドポイントをあらかじめ提供します。
| エンドポイント | メソッド | 説明 |
|---|---|---|
/register | POST | 新しいユーザーアカウントを作成します |
/login | POST | 認証を行い、トークンを受け取ります |
/refresh | POST | 期限切れのアクセストークンを更新します |
/confirmEmail | GET | メールアドレスを確認します |
/manage/info | GET/POST | ユーザープロファイルを取得/更新します |
/manage/2fa | POST | 二要素認証を構成します |
OAuth 2.0/OpenID Connect
外部の ID プロバイダー(Entra ID、Auth0、Okta、Keycloak)に認証を委任するアプリケーションでは、OIDC ミドルウェアを構成します。
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";
});
注意点: MapInboundClaims = false を設定すると、Microsoft OIDC ハンドラーによる標準 JWT クレームの再マッピング(例:sub から http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier へのマッピング)を防げます。ID プロバイダーから受け取った元のクレーム型を保持するには、これを false に設定してください。
JWT ベアラートークン認証
クライアントが Authorization ヘッダーで JWT を送信する、API 専用のシナリオでは次のようにします。
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();
ポリシーベースの認可
builder.Services.AddAuthorizationBuilder()
.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin"))
.AddPolicy("PremiumUser", policy =>
policy.RequireClaim("subscription", "premium"))
.SetFallbackPolicy(new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build());
パスキー/WebAuthn(.NET 10)
.NET 10 では、パスワードレス認証のためのパスキー(WebAuthn/FIDO2)サポートが組み込まれています。パスキーは公開鍵暗号方式を使用し、フィッシングに耐性があります。
// .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
パスキー登録フロー
- クライアントが
/passkey/register/optionsを呼び出し、PublicKeyCredentialCreationOptionsチャレンジを取得します - クライアントが Web Authentication API(
navigator.credentials.create)を使用して資格情報を作成します - クライアントがアテステーション応答を
/passkey/registerに送信します - サーバーが資格情報を検証して保存します
パスキー認証フロー
- クライアントが
/passkey/login/optionsを呼び出し、PublicKeyCredentialRequestOptionsチャレンジを取得します - クライアントが
navigator.credentials.getを使用してチャレンジに署名します - クライアントがアサーション応答を
/passkey/loginに送信します - サーバーがアサーションを検証し、セッション/トークンを発行します
主な利点: フィッシングされるパスワードがなく、サーバー側に保存される資格情報はなく(公開鍵のみ)、リプレイ攻撃への耐性が組み込まれています。
CORS ポリシー
クロスオリジンリソース共有(CORS)は、API の呼び出しを許可するオリジンを制御します。必ず明示的な名前付きポリシーを使用し、本番環境では AllowAnyOrigin() を決して使用しないでください。
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");
CORS でよくある落とし穴
AllowAnyOrigin()+AllowCredentials()は ASP.NET Core によって実行時に拒否されます。ただし、SetIsOriginAllowed(_ => true)+AllowCredentials()はすべてのオリジンを警告なく許可するため、このパターンは絶対に使用しないでください。- プリフライトのキャッシュ:
SetPreflightMaxAgeを設定しない場合、ブラウザーはクロスオリジンリクエストのたびに OPTIONS リクエストを送信します。遅延を減らすため、妥当なキャッシュ期間(10~60 分)を設定してください。 - 認証情報を伴うワイルドカードヘッダー: ASP.NET Core では
AllowAnyHeader()とAllowCredentials()を組み合わせて使用できますが、一部のブラウザーでは予期しない動作をする可能性があります。明示的なヘッダーリストを使用してください。 - CORS ミドルウェアの順序:
UseCors()はUseRouting()の後、UseAuthorization()の前に呼び出す必要があります。
コンテンツセキュリティポリシー(CSP)
コンテンツセキュリティポリシーヘッダーは、ブラウザーが読み込めるリソースを制御し、XSS、クリックジャッキング、その他のインジェクション攻撃を防ぎます。
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();
});
HTML レスポンスを提供する API(Razor Pages、Blazor Server)では、ノンスを使用した、より許容範囲の広い CSP を使用してください。
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();
});
レート制限
ASP.NET Core には、組み込みのレート制限ミドルウェア(Microsoft.AspNetCore.RateLimiting、.NET 7+)が含まれています。利用できるアルゴリズムは、固定ウィンドウ、スライディングウィンドウ、トークンバケット、同時実行数リミッターの 4 種類です。
固定ウィンドウ
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");
スライディングウィンドウ
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;
});
});
トークンバケット
builder.Services.AddRateLimiter(options =>
{
options.AddTokenBucketLimiter("token", limiterOptions =>
{
limiterOptions.TokenLimit = 100;
limiterOptions.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
limiterOptions.TokensPerPeriod = 10;
limiterOptions.QueueLimit = 0;
});
});
同時実行数リミッター
builder.Services.AddRateLimiter(options =>
{
options.AddConcurrencyLimiter("concurrent", limiterOptions =>
{
limiterOptions.PermitLimit = 10; // Max 10 concurrent requests
limiterOptions.QueueLimit = 5;
limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
});
});
ユーザーごとのレート制限
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)
});
});
});
注意点: 正しく適用するには、UseRateLimiter() を UseRouting() の後、UseAuthorization() およびエンドポイントのマッピングの前に呼び出す必要があります。
エージェントに関する注意点
- 本番環境の CORS ポリシーで
AllowAnyOrigin()を使用しないでください。常にオリジンを明示的に指定してください。CORS のセキュリティ上の影響については、[skill:dotnet-security-owasp] を参照してください。 - 外部 OIDC プロバイダーを使用する場合は、
MapInboundClaims = falseの設定を忘れないでください。設定しないと、クレームの種類が長い XML 名前空間 URI に再マッピングされ、ロールと名前の検索に失敗します。 - JWT 署名キーをソースコードや
appsettings.jsonにハードコードしないでください。開発環境ではユーザーシークレットを、本番環境では環境変数またはマネージド ID を使用してください。[skill:dotnet-secrets-management] を参照してください。 ClockSkewをTimeSpan.Zeroに設定しないでください。トークン発行者と検証側のわずかな時刻のずれによって、誤って 401 エラーが発生します。1~2 分に設定してください。- ミドルウェアの順序を忘れないでください。
UseAuthentication()はUseAuthorization()より前に、UseCors()はUseAuthorization()より前に呼び出す必要があります。 - 本番環境で
AllowAnyMethod()とAllowAnyHeader()を併用しないでください。最小権限の原則に従い、許可するメソッドとヘッダーを明示的に列挙してください。 - 認証エンドポイントのレート制限を省略しないでください。
/loginと/registerは、ブルートフォース攻撃の標的になりやすいエンドポイントです。クレデンシャルスタッフィングを防ぐため、レート制限を適用してください。 - 認証処理で例外を利用した拒否を行わないでください。攻撃者が制御できる入力には、防御的な解析(
TryFromBase64String、長さの検証)を使用してください。
前提条件
- .NET 8.0+(Identity API エンドポイント、JWT ベアラー、CORS、レート制限の LTS 基準)
- パスキー/WebAuthn のサポートには .NET 10.0
- JWT ベアラー認証には
Microsoft.AspNetCore.Authentication.JwtBearer - OIDC 統合には
Microsoft.AspNetCore.Authentication.OpenIdConnect Microsoft.AspNetCore.RateLimiting(.NET 7+ の共有フレームワークに含まれています)