9

I followed http://bitoftech.net/2014/12/15/secure-asp-net-web-api-using-api-key-authentication-hmac-authentication/ to do a custom authentication filter.

Everything is working correctly but I cannot get the server to say anything upon a 401. It correctly gives the www-authenicate header and status code 401 but no content/body.

I tried using AuthenticationFailureResult the from http://www.asp.net/web-api/overview/security/authentication-filters but did not help. I converted my AuthenticateAsync to async and ignored the await warning.

This is my current work around, the code in comments is what I -wish- I could do, that is mostly have it use whatever formatter

//request.CreateResponse(HttpStatusCode.Unauthorized, new { Error = true, Message = "Token is invalid" });
HttpContext.Current.Response.ContentType = "application/json";
HttpContext.Current.Response.Write("{ \"Error\" = true, \"Message\" = \"Token is invalid\" }");
context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], request);
ParoX
  • 5,685
  • 23
  • 81
  • 152

2 Answers2

12

There two options to do this: quick but brute and longer but more elegant

A. Modify HttpResponse directly:

HttpContext.Current.Response.StatusCode = 401;
HttpContext.Current.Response.Write("some content");

B. Implement IHttpActionResult and set a Content property of a HttpResponseMessage in that class:

public class AuthenticationFailureResult : IHttpActionResult
{
    public AuthenticationFailureResult(object jsonContent, HttpRequestMessage request)
    {
        JsonContent = jsonContent;
        Request = request;
    }

    public HttpRequestMessage Request { get; private set; }

    public Object JsonContent { get; private set; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute());
    }

    private HttpResponseMessage Execute()
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
        response.RequestMessage = Request;
        response.Content = new ObjectContent(JsonContent.GetType(), JsonContent, new JsonMediaTypeFormatter());
        return response;
    }
}

Then you'll be able to use it like this:

context.ErrorResult = new AuthenticationFailureResult(new { Error = true, Message = "Token is invalid" }, request);

Note: If you want to use anonymous types for JsonContent make sure that AuthenticationFailureResult implemented in the same library.

Vova
  • 1,356
  • 1
  • 12
  • 26
  • This seems like the hacky way, as it doesn't use my JSON parser. My attempt was `request.CreateResponse(HttpStatusCode.Unauthorized, new { Error = true, Message = "Token is invalid", });` but I can't get it to work. – ParoX Sep 30 '15 at 20:17
  • There is a `AuthenticationFailureResult` class in an example from your second link. If you have implemented IActionResult like in that example you can set `Content` property of `HttpResponseMessage` class there in `Execute()` method. – Vova Sep 30 '15 at 22:38
  • This did not give me any output when I set the Content property – ParoX Oct 05 '15 at 22:04
  • This did not give me JSON output when I set the Content property. I have to use Content = new StringContent which bypasses the JSON formatter, so setting content directly isn't what I want. – ParoX Oct 05 '15 at 22:11
  • Yeah, It's really puzzling. I found this topic though http://stackoverflow.com/questions/12563576/web-api-content-in-httpresponsemessage. It says that content should be set with `MediaTypeFormatter` like this: `response.Content = new ObjectContent(typeof(T), objectInstance, mediaTypeFormatter);` – Vova Oct 05 '15 at 22:11
  • That put me in the right direction, finally I was able to do `Content = new ObjectContent(jsonContent.GetType(), jsonContent, new JsonMediaTypeFormatter())` Which was able to render `jsonContent` given I passed it correctly to the `AuthenticationFailureResult` class. If you could readvise your answer to account for this, I will mark it as accepted with bounty. – ParoX Oct 05 '15 at 22:21
  • I'm glad that this helped. Give me an hour and I'll update the answer :) – Vova Oct 05 '15 at 22:25
4

If you want to attach a message along with the 401 response, I think you should do it like this. This is how I did in my project:

public class TokenAuthenticationAttribute : AuthorizeAttribute
{
    protected override bool IsAuthorized(HttpActionContext actionContext)
    {
        // Do your authentication here
    }

    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
        if (actionContext == null)
        {
            throw new ArgumentNullException("actionContext", "Null actionContext");
        }

        actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
        actionContext.Response.Headers.Add("AuthenticationStatus", "NotAuthorized");
        actionContext.Response.ReasonPhrase = "Authentication failed";

        // Create your content here (I use Student class as an example)
        actionContext.Response.Content = new ObjectContent(
            typeof (Student),
            new Student("Forte", 23),
            new JsonMediaTypeFormatter());
    }
}

Student class:

public class Student
{
    public string Name { get; set; }
    public int Age { get; set; }

    public Student(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

Then, I use it like this:

[TokenAuthentication]
public class DocumentController : ApiController
{
    // Your code here
}

EDIT!!!

Thanks to wal suggestion in the comment, here is a shorter way of attaching message to the response:

actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized,
                new {Error = true, Message = "Token is invalid"});

Then, your response message will be:

{"$id":"1","error":true,"message":"Token is invalid"}

By doing this way, you don't have to set actionContext.Response.Content anymore.

Triet Doan
  • 11,455
  • 8
  • 36
  • 69
  • pls note to return JSON instead of `StringContent` you can use `actionContext.Response = actionContext.Request.CreateResponse(new {Error = true, Message = "Token is invalid"});` – wal Oct 02 '15 at 02:46
  • that edit isnt much better really. pass your object into the `Request.CreateResponse` call (or use an anon class). you dont need `JsonMediaTypeFormatter` - if you do it my way then it will honor what was requested by the callee in the `Accept` header – wal Oct 02 '15 at 03:11
  • Sorry but I don't get it. Could you suggest a better way? :) – Triet Doan Oct 02 '15 at 03:16
  • can you see this line that you wrote? `actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);` replace that with what i wrote in my first comment, and then delete this line `actionContext.Response.Content...` – wal Oct 02 '15 at 03:18
  • I can't, it shows me error because the first argument must be a `HttpStatusCode`. – Triet Doan Oct 02 '15 at 03:22
  • 1
    try`actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, new { Error = true, Message = "Token is invalid" })` – wal Oct 02 '15 at 03:25
  • `context.Response` doesn't exist in the scope of `AuthenticateAsync` because it takes in a context of type `HttpAuthenticationContext` – ParoX Oct 05 '15 at 21:51
  • I was able to have it use `context.ActionContext.Response` but setting that to `context.ActionContext.Request.CreateResponse` or `context.Request.CreateResponse` does not output anything. It would seem upon setting `ErrorResult` it ignores the actioncontext. You have to use the official way of setting error output as with my example of `AuthenticationFailureResult` (A google of that class name yields many people using it) but it I cannot get it to work. – ParoX Oct 05 '15 at 22:01