0

I have an ASP.NET Core API that adds two headers to its response callback_uri and redirect_uri.

The strange thing (to me) is that in my AJAX call to the service, the headers are part of the JSON data, as a headers array, rather than the request object itself. I cannot use jqxhr.getResponseHeader(...) and therefore must interrogate the headers array manually within the response data.

Because the StatusCode is also part of the data it means my AJAX success callback is always called, even when I'm testing for a 400 bad request response, which makes testing less simple.

Web API controller action:

[HttpGet, Route("Authenticate")]
public HttpResponseMessage Authenticate(string applicationId)
{
    HttpResponseMessage response;

    if(!_security.IsApplicationIdValid(applicationId))
    {
        response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest);

        response.ReasonPhrase = ErrorMessages.INVALID_APPLICATION_ID;

        return response;
    }

    IAuthenticationProvider authProvider = _security.GetAuthenticationProvider();

    response = new HttpResponseMessage(System.Net.HttpStatusCode.Redirect);

    response.Headers.Add(HeaderKeyNames.CALLBACK_URI_KEY_NAME, authProvider.GetCallbackUrl());

    response.Headers.Add(HeaderKeyNames.AUTHENTICATION_SERVICE_REDIRECT_URI_KEY_NAME, authProvider.GetUrl());

    return response;
}

AJAX code:

var settings = {

    data: { "applicationId": applicationId },
    success: successCallback, // at the moment just writes to console
    error: errorCallback, // at the moment just writes to console
    method: "GET"
};

$.ajax(url, settings);

Am I doing something wrong on the server-side?

2
  • May you show the ajax call code? Commented Feb 8, 2019 at 12:15
  • I left it out because it's so trivial, but I'll add it. Commented Feb 8, 2019 at 12:22

3 Answers 3

1

You can use a combination of ResultFilters and ServiceFilterAttribute to add your custom headers. This is particularly useful because:

  1. ServiceFilter enables you to have DI access in your ResultFilter.
  2. You can apply it as an Attribute in the actions you want
  3. You can test it.

Putting all together:

  1. Create the custom result filter class
public class CustomHeadersResultFilter : IResultFilter
{
    private readonly IMyService _myService;

    public CustomHeadersResultFilter(IMyService myService)
    {
        _myService = myService;
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add("my-header", _myService.GetData());

        // if under CORS, this need to be added otherwise you can't read the headers using xhr.getResponseHeader('my-header')
        context.HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "my-header");
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // can't add headers here, since it's too late in the pipeline
    }
}
  1. Register it in your Startup.ConfigureServices
services.AddTransient<IMyService, MyService>();

// our custom result filter
services.AddTransient<CustomHeadersResultFilter>();
  1. Apply the attribute in the action you want to return the custom headers
[HttpGet("{id}")]
[ServiceFilter(typeof(CustomHeadersResultFilter))]
public ActionResult Get(string id)
{
    if (id == "something-bad")
    {
        return BadRequest("invalid application id");
    }

    // return a 200 Ok. Check the other types if you want something different
    return Ok();
}

Testing all of this with a separate web application, doing an ajax request to the API, you can access the headers:

<script>

    var settings = { method: "GET" };

    $.ajax('http://localhost:61284/api/values/test', settings)
        .done(function (data, textStatus, xhr) {
            alert(xhr.getResponseHeader('my-header'));
        })
        .fail(function () {
            alert("error");
        });

</script>
Sign up to request clarification or add additional context in comments.

5 Comments

I deliberately decoupled from using the request pipeline since it makes unit testing very difficult.
Sure, I get you. This was just a quick example, but the same can be achieve by creating a middleware. Something like this can help you get started. andrewlock.net/adding-default-security-headers-in-asp-net-core or action filters: learn.microsoft.com/en-us/aspnet/core/mvc/controllers/…
@Lee you are probably doing a CORS request, right? If so, don't forget you need to add the Access-Control-Expose-Headers, otherwise, you can't read your custom headers using xhr.getResponseHeader
I have the UI and the services all under the same site in IIS, so unfortunately I don't think this is the problem. I'm guessing this is just the way Web API 2 handles HttpResponseMessage
Well.. that's strange and it's hard to tell much without seeing it. I'm updating my answer with a better approach, and I just tested and it works like a charm. Give it a run and see if works (try enabling cors..)
0

Add headers like this: (ofc change the type if needed or define your own)

response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");

Comments

0

What you're doing is creating a HttpResponseMessage object, serializing it to json and then returning it.
This is why the headers are in the json content, instead of the http response.

What you can do is someting like this:

[HttpGet, Route("Authenticate")]
public IActionResult Authenticate(string applicationId)
{

    if(!_security.IsApplicationIdValid(applicationId))
    {
        return BadRequest(ErrorMessages.INVALID_APPLICATION_ID);
    }

    IAuthenticationProvider authProvider = _security.GetAuthenticationProvider();

    this.Response.Headers.Add(HeaderKeyNames.CALLBACK_URI_KEY_NAME, authProvider.GetCallbackUrl());

    this.Response.Headers.Add(HeaderKeyNames.AUTHENTICATION_SERVICE_REDIRECT_URI_KEY_NAME, authProvider.GetUrl());

    return StatusCode(302);
}

2 Comments

I made the choice not to rely on Response so that I can unit test much more easily. I know it's being serialized as JSON as I can see it in the browser network sniffer. I think filters might be the only way forward, like jpgrassi has mentioned.
If you need this logic in several places then yes, filters would be a good approach.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.