Security utilities and middleware
$ dotnet add package Angelo.SecuritySecure-by-default security headers middleware for ASP.NET Core. Provides best-practice security headers with zero configuration, while offering a fluent API for customization when needed.
Add the Angelo.Security nuget package:
dotnet add package Angelo.Security
var app = builder.Build();
app.UseSecurity(); // That's it
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.UseSecurity(options =>
{
options.ContentSecurityPolicy
.AllowScripts("https://cdn.example.com")
.AllowInlineStyles();
});
app.UseSecurity(app.Environment, options =>
{
options.ContentSecurityPolicy.AllowFrames("https://youtube.com");
});
When you call app.UseSecurity() with no configuration, you get:
| Header | Value | Purpose |
|---|---|---|
X-Content-Type-Options | nosniff | Prevents MIME-type sniffing |
X-Frame-Options | DENY | Prevents clickjacking |
Referrer-Policy | strict-origin-when-cross-origin | Controls referrer information |
Strict-Transport-Security | max-age=31536000; includeSubDomains | Enforces HTTPS |
Content-Security-Policy | See below | Controls resource loading |
Permissions-Policy | Restrictive | Disables dangerous browser features |
Cross-Origin-Opener-Policy | same-origin | Isolates browsing context |
Cross-Origin-Resource-Policy | same-origin | Controls cross-origin resource sharing |
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
frame-src 'none';
frame-ancestors 'none';
form-action 'self';
base-uri 'self';
object-src 'none';
upgrade-insecure-requests;
The CSP configuration uses a fluent builder pattern for readability.
options.ContentSecurityPolicy
.AllowScripts("https://cdn.example.com", "https://analytics.example.com")
.AllowStyles("https://fonts.googleapis.com")
.AllowFonts("https://fonts.gstatic.com")
.AllowImages("https:", "data:", "blob:")
.AllowConnections("https://api.example.com", "wss://socket.example.com")
.AllowFrames("https://youtube.com", "https://vimeo.com")
.AllowMedia("https://media.example.com")
.AllowWorkers("https://worker.example.com")
.AllowManifest("https://app.example.com");
Use these sparingly—they weaken CSP protection:
options.ContentSecurityPolicy
.AllowInlineScripts() // Adds 'unsafe-inline' to script-src
.AllowInlineStyles() // Adds 'unsafe-inline' to style-src
.AllowEval(); // Adds 'unsafe-eval' to script-src
options.ContentSecurityPolicy
.AllowDataUri("img-src", "font-src") // Adds data: to specified directives
.AllowBlobUri("worker-src", "img-src"); // Adds blob: to specified directives
Control which sites can embed your page in an iframe:
// Allow specific parents to embed this page
options.ContentSecurityPolicy
.AllowFrameAncestors("https://parent-site.com", "https://partner.com");
For directives not covered by helper methods:
options.ContentSecurityPolicy
.SetDirective("require-trusted-types-for", "'script'")
.AddToDirective("script-src", "https://additional-source.com")
.RemoveDirective("object-src");
Test CSP changes without breaking your site:
options.ContentSecurityPolicy.ReportOnly = true;
options.ContentSecurityPolicy.ReportUri = "/api/csp-violations";
options.ContentSecurityPolicy.UpgradeInsecureRequests = false;
options.FrameOptions = FrameOptions.Deny; // Cannot be framed (default)
options.FrameOptions = FrameOptions.SameOrigin; // Can be framed by same origin
options.ReferrerPolicy = ReferrerPolicy.NoReferrer;
options.ReferrerPolicy = ReferrerPolicy.Origin;
options.ReferrerPolicy = ReferrerPolicy.StrictOriginWhenCrossOrigin; // default
// Also: NoReferrerWhenDowngrade, OriginWhenCrossOrigin, SameOrigin, StrictOrigin, UnsafeUrl
options.Hsts.Enabled = true; // default
options.Hsts.MaxAge = 31536000; // 1 year (default)
options.Hsts.IncludeSubDomains = true; // default
options.Hsts.Preload = false; // Enable when ready for HSTS preload list
// Cross-Origin-Opener-Policy
options.CrossOriginOpenerPolicy = CrossOriginOpenerPolicy.SameOrigin; // default
options.CrossOriginOpenerPolicy = CrossOriginOpenerPolicy.SameOriginAllowPopups;
options.CrossOriginOpenerPolicy = CrossOriginOpenerPolicy.UnsafeNone;
// Cross-Origin-Embedder-Policy
options.CrossOriginEmbedderPolicy = CrossOriginEmbedderPolicy.None; // default (disabled)
options.CrossOriginEmbedderPolicy = CrossOriginEmbedderPolicy.RequireCorp;
options.CrossOriginEmbedderPolicy = CrossOriginEmbedderPolicy.Credentialless;
// Cross-Origin-Resource-Policy
options.CrossOriginResourcePolicy = CrossOriginResourcePolicy.SameOrigin; // default
options.CrossOriginResourcePolicy = CrossOriginResourcePolicy.SameSite;
options.CrossOriginResourcePolicy = CrossOriginResourcePolicy.CrossOrigin;
options.RemoveServerHeader = true; // Removes Server header (default)
options.RemovePoweredByHeader = true; // Removes X-Powered-By header (default)
Controls which browser features your site can use. Restrictive by default.
options.PermissionsPolicy
.Disable("geolocation") // Disable entirely
.AllowSelf("camera") // Allow for same origin only
.AllowAll("fullscreen") // Allow for all origins
.Allow("microphone", "https://call.example.com") // Allow for specific origin
.Remove("accelerometer"); // Remove from policy
Pre-configured security settings for common scenarios.
Maximum security for sensitive applications (banking, healthcare):
app.UseSecurity(options => options.ApplyPreset(SecurityPresets.Strict));
default-src 'none'Balanced security and compatibility (default behavior with inline styles):
app.UseSecurity(options => options.ApplyPreset(SecurityPresets.Standard));
Optimized for REST APIs:
app.UseSecurity(options => options.ApplyPreset(SecurityPresets.Api));
For SPAs with external API connections:
app.UseSecurity(options => options.ApplyPreset(SecurityPresets.SinglePageApp));
For marketing sites with analytics, videos, social embeds:
app.UseSecurity(options => options.ApplyPreset(SecurityPresets.Marketing));
Relaxed settings for legacy applications being migrated:
app.UseSecurity(options => options.ApplyPreset(SecurityPresets.Legacy));
app.UseSecurity(options =>
{
options.ApplyPreset(SecurityPresets.Marketing);
options.ContentSecurityPolicy.AllowScripts("https://my-custom-cdn.com");
options.Hsts.Preload = true;
});
The middleware automatically detects the development environment and applies appropriate settings.
app.UseSecurity(options =>
{
// In development: CSP automatically allows ws:/wss: and localhost connections
options.ContentSecurityPolicy.AllowFrames("https://youtube.com");
});
app.UseSecurity(app.Environment, options =>
{
options.ContentSecurityPolicy.AllowFrames("https://youtube.com");
});
When IsDevelopment() is true:
connect-src includes ws:, wss:, http://localhost:*, https://localhost:*script-src includes 'unsafe-inline'style-src includes 'unsafe-inline'app.Use(async (context, next) =>
{
var headers = context.Response.Headers;
headers["X-Content-Type-Options"] = "nosniff";
headers["X-Frame-Options"] = "DENY";
headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains";
string csp;
if (app.Environment.IsDevelopment())
{
csp = "default-src 'self' ws: wss:; " +
"script-src 'self' 'unsafe-inline' https://esm.sh; " +
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " +
"font-src 'self' https://fonts.gstatic.com; " +
"img-src 'self' data: https:; " +
"connect-src 'self' ws: wss: http://localhost:* https://esm.sh; " +
"form-action 'self'; " +
"base-uri 'self'; " +
"frame-src https://www.youtube.com https://www.youtube-nocookie.com; " +
"object-src 'none'; " +
"frame-ancestors 'none';";
}
else
{
csp = "default-src 'self'; " +
"script-src 'self' 'unsafe-inline' https://esm.sh; " +
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " +
"font-src 'self' https://fonts.gstatic.com; " +
"img-src 'self' data: https:; " +
"connect-src 'self'; " +
"frame-src https://www.youtube.com https://www.youtube-nocookie.com; " +
"form-action 'self'; " +
"base-uri 'self'; " +
"object-src 'none'; " +
"frame-ancestors 'none';";
}
headers["Content-Security-Policy"] = csp;
await next();
});
app.UseSecurity(options =>
{
options.ContentSecurityPolicy
.AllowInlineScripts()
.AllowInlineStyles()
.AllowScripts("https://esm.sh")
.AllowStyles("https://fonts.googleapis.com")
.AllowFonts("https://fonts.gstatic.com")
.AllowImages("data:", "https:")
.AllowConnections("https://esm.sh")
.AllowFrames("https://www.youtube.com", "https://www.youtube-nocookie.com");
});
Development settings (ws:/wss:/localhost) are applied automatically.
| Method | Description |
|---|---|
UseSecurity() | Apply secure defaults |
UseSecurity(Action<SecurityOptions>) | Apply with configuration |
UseSecurity(IWebHostEnvironment, Action<SecurityOptions>?) | Apply with explicit environment |
| Property | Type | Default | Description |
|---|---|---|---|
ContentSecurityPolicy | CspOptions | Restrictive | CSP configuration |
FrameOptions | FrameOptions | Deny | X-Frame-Options value |
NoSniff | bool | true | X-Content-Type-Options: nosniff |
ReferrerPolicy | ReferrerPolicy | StrictOriginWhenCrossOrigin | Referrer-Policy value |
Hsts | HstsOptions | Enabled, 1 year | HSTS configuration |
PermissionsPolicy | PermissionsPolicyOptions | Restrictive | Permissions-Policy configuration |
CrossOriginOpenerPolicy | CrossOriginOpenerPolicy | SameOrigin | COOP value |
CrossOriginEmbedderPolicy | CrossOriginEmbedderPolicy | None | COEP value |
CrossOriginResourcePolicy | CrossOriginResourcePolicy | SameOrigin | CORP value |
RemoveServerHeader | bool | true | Remove Server header |
RemovePoweredByHeader | bool | true | Remove X-Powered-By header |
| Method | Description |
|---|---|
AllowScripts(params string[]) | Add sources to script-src |
AllowStyles(params string[]) | Add sources to style-src |
AllowImages(params string[]) | Add sources to img-src |
AllowFonts(params string[]) | Add sources to font-src |
AllowConnections(params string[]) | Add sources to connect-src |
AllowFrames(params string[]) | Add sources to frame-src |
AllowFrameAncestors(params string[]) | Add sources to frame-ancestors |
AllowFormAction(params string[]) | Add sources to form-action |
AllowMedia(params string[]) | Add sources to media-src |
AllowWorkers(params string[]) | Add sources to worker-src |
AllowManifest(params string[]) | Add sources to manifest-src |
AllowInlineScripts() | Add 'unsafe-inline' to script-src |
AllowInlineStyles() | Add 'unsafe-inline' to style-src |
AllowEval() | Add 'unsafe-eval' to script-src |
AllowDataUri(params string[]) | Add data: to specified directives |
AllowBlobUri(params string[]) | Add blob: to specified directives |
SetDirective(string, params string[]) | Set directive value (replaces existing) |
AddToDirective(string, params string[]) | Add to existing directive |
RemoveDirective(string) | Remove directive entirely |
ApplyDevelopmentSettings() | Apply dev-friendly settings |
| Method | Description |
|---|---|
Disable(string) | Disable feature entirely |
AllowSelf(string) | Allow feature for same origin |
AllowAll(string) | Allow feature for all origins |
Allow(string, params string[]) | Allow feature for specific origins |
Remove(string) | Remove feature from policy |
| Preset | Use Case |
|---|---|
Strict | Banking, healthcare, high-security apps |
Standard | General web applications |
Api | REST APIs |
SinglePageApp | React, Vue, Angular apps |
Marketing | Marketing sites with analytics/embeds |
Legacy | Legacy apps being migrated |