I’m working on an ASP.NET Core application where I need to handle Content Security Policy (CSP) violation reports. I have set up a CSP header to report violations to a specific endpoint and created a corresponding controller to handle the reports. However, I’m encountering issues with deserializing the incoming JSON payload. Despite setting the Content-Type to application/csp-report, the request object in my controller action is null, and the properties of my model are not being populated correctly.
Setup Details:
CSP Header Middleware:
I added a middleware to set the CSP header, which includes the report-uri directive:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.Use(async (ctx, next) =>
{
ctx.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self'; report-uri /csp-report;");
await next();
});
// Other middleware...
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllers();
});
}
Controller to Handle CSP Reports:
I created a controller to handle POST requests at the /csp-report endpoint:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using IMSLIB.Paywall.Entities.Definitions.CspReporting;
using Microsoft.AspNetCore.Authorization;
namespace IMSAPP.Paywall.UI.Controllers
{
[Route("csp-report")]
[ApiController]
public class CspReportController : ControllerBase
{
private readonly ILogger<CspReportController> _logger;
public CspReportController(ILogger<CspReportController> logger)
{
_logger = logger;
}
[HttpPost]
[AllowAnonymous]
public IActionResult CspReport([FromBody] CspReportRequest request)
{
if (request == null || request.CspReport == null)
{
return BadRequest("Invalid CSP report");
}
// Log the CSP report
_logger.LogWarning($"CSP Violation: Document URI: {request.CspReport.DocumentUri}, Blocked URI: {request.CspReport.BlockedUri}");
return Ok();
}
}
}
Model Classes:
My model classes for deserializing the CSP report:
using Newtonsoft.Json;
namespace IMSLIB.Paywall.Entities.Definitions.CspReporting
{
public class CspReportRequest
{
[JsonProperty("cspReport")]
public CspReport CspReport { get; set; }
}
public class CspReport
{
[JsonProperty("document-uri")]
public string DocumentUri { get; set; }
[JsonProperty("referrer")]
public string Referrer { get; set; }
[JsonProperty("blocked-uri")]
public string BlockedUri { get; set; }
[JsonProperty("violated-directive")]
public string ViolatedDirective { get; set; }
[JsonProperty("effective-directive")]
public string EffectiveDirective { get; set; }
[JsonProperty("original-policy")]
public string OriginalPolicy { get; set; }
[JsonProperty("disposition")]
public string Disposition { get; set; }
[JsonProperty("status-code")]
public int StatusCode { get; set; }
[JsonProperty("script-sample")]
public string ScriptSample { get; set; }
[JsonProperty("line-number")]
public int LineNumber { get; set; }
[JsonProperty("column-number")]
public int ColumnNumber { get; set; }
[JsonProperty("source-file")]
public string SourceFile { get; set; }
}
}
Configuring the Input Formatter:
In Startup.cs, I configured the SystemTextJsonInputFormatter to support the application/csp-report media type:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
using System.Linq;
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.Configure<MvcOptions>(options =>
{
var jsonInputFormatter = options.InputFormatters.OfType<SystemTextJsonInputFormatter>().First();
jsonInputFormatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/csp-report"));
});
// Other service registrations...
}
Testing the Endpoint:
To test the endpoint, I used the following JavaScript code to send a CSP violation report:
fetch('/csp-report', {
method: 'POST',
headers: {
'Content-Type': 'application/csp-report'
},
body: JSON.stringify({
cspReport: {
"document-uri": "http://example.com/test",
"referrer": "",
"violated-directive": "script-src-elem",
"effective-directive": "script-src-elem",
"original-policy": "default-src 'none'; script-src 'self';",
"disposition": "report",
"blocked-uri": "http://malicious.com/script.js",
"line-number": 1,
"column-number": 1,
"source-file": "http://example.com/test"
}
})
}).then(response => {
if (response.ok) {
console.log("CSP report sent successfully.");
} else {
console.error("Failed to send CSP report.");
}
});
Problem:
Despite configuring everything, the CspReportRequest object in the controller action remains null, and the properties of the CspReport object are not being populated correctly.
Request Payload:
Here is the payload sent in the request:
{
"cspReport": {
"document-uri": "http://example.com/test",
"referrer": "",
"violated-directive": "script-src-elem",
"effective-directive": "script-src-elem",
"original-policy": "default-src 'none'; script-src 'self';",
"disposition": "report",
"blocked-uri": "http://malicious.com/script.js",
"line-number": 1,
"column-number": 1,
"source-file": "http://example.com/test"
}
}
Error Observed:
The request object in the controller action is null, resulting in a 400 Bad Request response:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "Bad Request",
"status": 400,
"traceId": "|553d9216-43a24081a09550b4."
}
What I’ve Tried:
Ensured that the JSON structure matches the model properties.
Added JsonProperty attributes to match the JSON property names.
Configured SystemTextJsonInputFormatter to support the application/csp-report media type.
Verified that the payload is correctly structured.
Question:
How can I correctly deserialize the incoming application/csp-report payload into my CspReportRequest model in ASP.NET Core?
Additional Context:
ASP.NET Core 5.0
Using System.Text.Json for JSON serialization/deserialization.
Any help or insights into why the deserialization is failing and how to fix it would be greatly appreciated.
Darko Cirkovic is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.