"415 Unsupported Media Type" for Content-Type "application/csp-report" in ASP.NET Core

I have a content security policy that causes Chrome to post a report, but the action that receives the report returns "415 Unsupported Media Type". I understand this is because the post has a Content-Type of "application/csp-report". How do I add this as a allowed content type in Core 3.1 (its basically just json).

Action

// https://anthonychu.ca/post/aspnet-core-csp/
[HttpPost][Consumes("application/csp-report")]
public IActionResult Report([FromBody] CspReportRequest request)
{
    return Ok();
}

Cut down version of model

public class CspReportRequest
{
    [JsonProperty(PropertyName = "csp-report")]
    public CspReport CspReport { get; set; }
}

public class CspReport
{
    [JsonProperty(PropertyName = "document-uri")]
    public string DocumentUri { get; set; }
}

The following example shows how to add support to the SystemTextJsonInputFormatter for handling additional media-types:

services.AddControllers(options =>
{
    var jsonInputFormatter = options.InputFormatters
        .OfType<SystemTextJsonInputFormatter>()
        .Single();

    jsonInputFormatter.SupportedMediaTypes.Add("application/csp-report");
});

This is a two-step process:

  1. Interrogate the configured list of input-formatters to find the SystemTextJsonInputFormatter.
  2. Add application/csp-report to its existing list of supported media-types (application/json, text/json, and application/*+json).

If you're using Json.NET instead of System.Text.Json, the approach is similar:

services.AddControllers(options =>
{
    var jsonInputFormatter = options.InputFormatters
        .OfType<NewtonsoftJsonInputFormatter>()
        .First();

    jsonInputFormatter.SupportedMediaTypes.Add("application/csp-report");
})

There are two small differences:

  1. The type is NewtonsoftJsonInputFormatter instead of SystemTextJsonInputFormatter.
  2. There are two instances of this type in the collection, so we target the first (see this answer for the specifics).

See Input Formatters in the ASP.NET Core docs to learn more about those.

I would like to add that the accepted solution did not work for me. (.NET Core 3.1) I have the exact same use case regarding the CSP reports. When trying to use NewtonSoft and modifying the InputFormatter NewtonsoftJsonInputFormatter to accept media header type application/csp-report, I would always get an exception saying the inputformatter could not be found (with or without .AddNewtonsoftJson();)

I managed to solve the problem by doing the following:

services.AddControllers().AddNewtonsoftJson();
services.AddOptions<MvcOptions>()
      .PostConfigure<IOptions<JsonOptions>, IOptions<MvcNewtonsoftJsonOptions>, ArrayPool<char>, ObjectPoolProvider, ILoggerFactory>(
          (mvcOptions, jsonOpts, newtonJsonOpts, charPool, objectPoolProvider, loggerFactory) =>
          {
              var formatter = mvcOptions.InputFormatters.OfType<NewtonsoftJsonInputFormatter>().First(i => i.SupportedMediaTypes.Contains("application/json"));
              formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/csp-report"));
              mvcOptions.InputFormatters.RemoveType<NewtonsoftJsonInputFormatter>();
              mvcOptions.InputFormatters.Add(formatter);
          });

My model and the controller action are the same as the ones posted in the question.

(I derived my solution from How to configure two JSON serializers and select the correct one based on the route)

I had the same problem last week and found an alternative solution using my own custom formatter:

using CspReportLogger.Models;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
using System;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace CspReportLogger.Formatters
{
  public class CSPReportInputFormatter : TextInputFormatter
  {
    public CSPReportInputFormatter()
    {
      // Specify the custom media type.
      SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/csp-report"));
      SupportedEncodings.Add(Encoding.UTF8);
    }

    public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding effectiveEncoding)
    {
      // Deserialize the body using our models and the JsonSerializer.
      CspReportRequest report = await JsonSerializer.DeserializeAsync<CspReportRequest>(context.HttpContext.Request.Body);
      return await InputFormatterResult.SuccessAsync(report);
    }
  }
}

Which has to be registered in Startup.cs of course:

    public void ConfigureServices(IServiceCollection services)
    {
      services.AddControllers(options =>
      {
        options.InputFormatters.Insert(0, new CSPReportInputFormatter());
      });
    }

I wish I had seen Kirk Larkin's solution earlier, as it obviously is more concise.

I suppose the custom formatter solution is helpful, if you want to accept body types that aren't valid json though.

Comments
  • This worked for me with a slight adjustment I used JsonInputFormatter instead of NewtonsoftJsonInputFormatter