59

Is it possible to access post or get parameters via the HttpActionContext object?

I have a set of sensors that loggs data to a web server that provides a REST api. I would like to introduce some sort of authentication/authorization by letting the sensors include their hardware id in the data and then make a lookup in a database to see if the id exists or not. Since the API provides many web api action methods I would ideally like to use a custom authorization attribute

public class ApiAuthorizationFilter : AuthorizeAttribute
{
    protected override bool IsAuthorized(HttpActionContext actionContext)
    {
        return false;
    }
}

How can I access the post/get data from the actionContext?

EDIT: Example of POST

POST /Api/api/ActionMethod/ HTTP/1.1\r\n
Content-Type: application/json\r\n
Host: localhost\r\n
Accept: */*\r\n
Content-Length:52\r\n
\r\n
{"Id": '121a222bc', "Time": '2012-02-02 12:00:00'}\r\n

Have a nice day!

olif
  • 3,221
  • 2
  • 25
  • 23

5 Answers5

65

Due to its nature the AuthoriseAttribute looks like it is called in the pipeline before the model binders and parameter bindings have run. You also run into issues when you access the Request.Content and read from it... this can only be done once and if you are going to try it in your auth attribute you may break the mediaTypeFormater...

in WebAPI, the request body (an HttpContent) may be a read-only, infinite, non-buffered, non-rewindable stream.

Update There are different ways of specifying the execution context... http://msdn.microsoft.com/en-us/library/system.web.http.filters.filterscope(v=vs.108).aspx. The AuthoriseAttribute is "Global" and therefore it is hit too early to access the action information.

Given you want access to the model and parameters you can change your approach slightly and use an OnActionExecuting filter ("Action" filter scope) instead and throw a 401 or 403 based on your validation.

This filter is called later in the execution process and you therefore have full access to the bound data.

Very simple example below:

public class ApiAuthorizationFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        Foo model = (Foo)actionContext.ActionArguments["model"];
        string param1 = (string)actionContext.ActionArguments["param1"];
        int param2 = (int)actionContext.ActionArguments["param2"];

        if (model.Id != "1")
            throw new HttpResponseException(System.Net.HttpStatusCode.Forbidden);

        base.OnActionExecuting(actionContext);
    }
}

Example controller:

public class Foo
{
    public string Id { get; set; }
    public DateTime Time { get; set; }
}

public class FoosController : ApiController
{
    // PUT api/foos/5
    [ApiAuthorizationFilter]
    public Foo Put(int id, Foo model, [FromUri]string param1 = null, int? param2 = null)
    {
        return model;
    }
}

What the other answers were saying.... they are right you can, if you can access all you need on the URL, get at stuff via the request; however, I think the model and the request content should be left alone:

var queryStringCollection = HttpUtility.ParseQueryString(actionContext.Request.RequestUri.Query);

    //example for param1
    string param1 = queryStringCollection["param1"];
    //example for param2
    int param2 = int.Parse(queryStringCollection["param2"]);
    //Example of getting the ID from the URL
    var id = actionContext.Request.RequestUri.Segments.LastOrDefault();
Mark Jones
  • 12,156
  • 2
  • 50
  • 62
28

I accessed the context route data to get the parameters from within a custom AuthorizeAttribute when calling something like /api/client/123/users:

public class CustomAuthorizeAttribute : AuthorizeAttribute
  {
     protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
     {
        var clientId = Convert.ToInt32(actionContext.ControllerContext.RouteData.Values["clientid"]);

        // Check if user can access the client account.

     }
  }
simbolo
  • 7,279
  • 6
  • 56
  • 96
1

You can access query string values from your custom authorize attribute by using the following code:

public class ApiAuthorizationFilter : AuthorizeAttribute
{
    protected override void OnAuthorization(AuthorizationContext filterContext)
    {
        var querystring = filterContext.RequestContext.HttpContext.Request.QueryString;
        // Do what you need
    }
}
Christian Fritz
  • 20,641
  • 3
  • 42
  • 71
jmberon
  • 155
  • 3
  • 9
1

Although this question has already been answered. But in case someone else needs it, you can get the querystrings from ActionFilterAttribute like below:

public class ApiAuthorizationActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var queryParameters = actionContext.Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value);

        var some_value = queryParameters.ContainsKey("some_key")
                    ? queryParameters["some_key"] : string.Empty;

        // Log Action Filter call
        base.OnActionExecuting(actionContext);
    }
}

But usually how I build API authorizations are using headers and a custom verification logic by adding keys (unique strings) to the database against user/client etc.

public class ApiAuthorizationActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var headers = actionContext.Request.Headers.ToDictionary(x => x.Key, x => x.Value);

        string api_key = headers.ContainsKey("api_key") ? headers["api_key"].FirstOrDefault() : null;

        bool canAccessApi = IsValidKey(api_key);

        if (!canAccessApi)
            actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "You do not have access to this API. Please use a valid key.");

        // Log Action Filter call
        base.OnActionExecuting(actionContext);
    }

    private bool IsValidKey(string api_key)
    {
        //Build Access Control Logic here using database keys...
        return true;
    }
}
vohrahul
  • 1,123
  • 10
  • 17
  • 1
    Be wary of ToDictionary though. ?ids=1&ids=2 is a valid way to pass collections, so there may be no unique key. – Troels Larsen Jan 19 '16 at 15:15
  • @TroelsLarsen, You are right. But not sure if this was asked in the question. However, I second your opinion. Thanks – vohrahul Apr 20 '16 at 05:52
  • actionContext.Request is a instance of 'HttpRequestMessage' and does not contain the method'GetQueryNameValuePairs' – Alessandro Lallo Jun 05 '18 at 13:45
  • 1
    @AlessandroLallo, I have updated the answer to add, this is can be done using "ActionFilterAttribute" in which case the code works. You can check again. – vohrahul Jun 09 '18 at 20:31
-2

You should be able to get this information from actionContext.Request That is the way to get to the request data.

The posted data is in actionContext.Request.Content Or if it's a GET request you could get the querystring from actionContext.Request.RequestUri

David
  • 435
  • 4
  • 8