IM.ContentSecurityPolicy · Umbraco 13 to 18
A strict CSP without manual nonces
Content Security Policy headers and a fresh nonce for every request, for ASP.NET Core and Umbraco. The package adds the nonce to your script, style and link tags.
Three jobs, on every request
A nonce per request
- A random value from a cryptographically secure generator, available to your views and code.
A CSP header from your settings
- Content-Security-Policy, or Report-Only while you test, built from options you control.
Nonces on your tags
- Added to script, style and link tags by a tag helper, HTML helpers or view components.
Install and register
01
Add the package
One package for Umbraco 13 to 18 and plain ASP.NET Core.
dotnet add package IM.ContentSecurityPolicy02
Register the middleware
In Program.cs. Nonce generation comes first, so the header can use the nonce.
builder.Services.AddNonceServices();
var app = builder.Build();
app.UseNonceGeneration();
app.UseContentSecurityPolicy();03
Register the helpers once
In Views/_ViewImports.cshtml, so every view can add the nonce.
@addTagHelper *, IM.ContentSecurityPolicy
@using IM.ContentSecurityPolicy.ExtensionsIn your views
Four ways to add the nonce
01 Tag helper
Add asp-nonce="true" to script, style and link tags. The nonce of the current request is written for you.
<script src="~/js/site.js" asp-nonce="true"></script>
<style asp-nonce="true">
.hero { color: #3544b1; }
</style>
<link rel="stylesheet" href="~/css/site.css" asp-nonce="true" /> 02 HTML helpers
For tags you build in Razor. The helpers do not HTML-encode their input, so pass only trusted values.
@Html.ScriptWithNonce("~/js/site.js")
@Html.InlineScriptWithNonce("console.log('ready');")
@Html.StyleWithNonce("~/css/site.css")
@Html.InlineStyleWithNonce(".hero { color: #3544b1; }")
<script @Html.GetNonceAttribute()>
window.dataLayer = window.dataLayer || [];
</script> 03 View components
NonceScript, NonceInlineScript, NonceStyle and NonceInlineStyle. Each needs its view, for example Views/Shared/Components/NonceScript/Default.cshtml.
@await Component.InvokeAsync("NonceScript", new { src = "~/js/site.js" }) 04 Your own code
Inject INonceService. GetNonce() returns the nonce of the current request and HasNonce() tells you whether one exists.
public class ScriptsController(INonceService nonces) : Controller
{
public IActionResult Index()
{
ViewData["Nonce"] = nonces.GetNonce();
return View();
}
} Test first, then enforce
01
Report-Only while you test
The browser reports what the policy would block, and blocks nothing.
- Set ReportOnly and a ReportUri
- See which scripts and styles still lack a nonce
- Visitors notice nothing
02
Enforce
Turn Report-Only off once the reports are clean.
- The browser blocks anything without a valid nonce or allowed source
- A fresh nonce on every request
- Keep the ReportUri to catch regressions
Set the sources
Options
Set the sources
{nonce} is replaced on every request.
builder.Services.AddNonceServices(options =>
{
options.ScriptSrc = new[] { "'self'", "'nonce-{nonce}'", "https://cdn.example.com" };
options.ReportOnly = true;
options.ReportUri = "/csp-report";
});Builder
Start from the strict preset
CspOptionsBuilder gives you a strict policy to extend.
var options = CspOptionsBuilder.CreateStrict()
.WithScriptSrc("'self'", "'nonce-{nonce}'")
.WithReportUri("/csp-report")
.Build();One package, Umbraco 13 to 18
| Umbraco | .NET | Package version |
|---|---|---|
| 13 LTS, 14 | 8 | 2.x |
| 15, 16 | 9 | 2.x |
| 17 LTS, 18 | 10 | 2.x |
| 10 to 12 | 6, 7 | 1.0.2 |
On our own site
01 · Response header
The policy ideamakers.pl sends
Scripts and styles run only from our own domain or with the nonce of the request. No 'unsafe-inline', no plugins, no framing by other sites.
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-qTQx0elxVNpegG/7B+tAYA==' https://challenges.cloudflare.com;
style-src 'self' 'nonce-qTQx0elxVNpegG/7B+tAYA==' 'sha256-Kh2l25Sy4DYc/g2Mm3KwI0W8tow97xzNP6zUE8/2UHE=';
img-src 'self' data:;
font-src 'self' data:;
connect-src 'self';
frame-src 'self' https://challenges.cloudflare.com;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'self'02 · Two requests
A new nonce every time
The same page requested twice gets two different values, so a nonce copied from one response is useless in the next.
GET https://ideamakers.pl/en/contact/
script-src 'self' 'nonce-74KxrM8xVoaTobkUhxHTZA==' ...
GET https://ideamakers.pl/en/contact/
script-src 'self' 'nonce-qTQx0elxVNpegG/7B+tAYA==' ...03 · Page source
The nonce on every script
Structured data, the consent state and inline scripts all carry the nonce of that response.
<script type="application/ld+json" nonce="qTQx0elxVNpegG/7B+tAYA==">
<script id="im-consent-state" type="application/json" nonce="qTQx0elxVNpegG/7B+tAYA==">
<script nonce="qTQx0elxVNpegG/7B+tAYA==">Questions, bugs and ideas
Want a strict CSP on your Umbraco site?
We can set up the policy, move inline scripts to nonces and test it in Report-Only first.