Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Securing ASP.NET MVC controller action which returns JSON

I have an MVC3 application, and my controller actions are secured using the [Authorize] attribute. So far, so good, forms auth works great. Now I want to add a JSON API to my application so some actions are accessible to non-browser clients.

I'm having trouble figuring out the 'right' design.

1) Each user has secret API key.

2) User ID 5 calls http://myapp.com/foocontroller/baraction/5?param1=value1&param2=value2&secure_hash=someValue. Here, secure_hash is simply the SHA1 hash of param1 and param2's values appended with the secret API key for the user

2) /foocontroller/baraction will be decorated with [CustomAuthorize]. This will be an implementation of AuthorizeAttribute which will check if the request is coming in as JSON. If it is, it will check the hash and see if it matches. Otherwise, if the request is HTML, then I call into existing authorization.

I am not at all sure if this will work. Is it normal to pass a secure hash in the query string or should I be passing it in as an HTTP header? Is it better to use HTTP basic auth instead of a hash made using the secret API key?

Tips from anyone who has made a web API using ASP.NET MVC would be welcome!

like image 283
Bilal and Olga Avatar asked May 01 '11 22:05

Bilal and Olga


1 Answers

I pass the secret API key along with username and password in the request body. Once authorized, a token is generated and the client has to pass that in the Authorization header. This gets checked in the base controller on each request.

  1. Client calls myapp.com/authorize which return auth token.
  2. Client stores auth token locally.
  3. Client calls myapp.com/anycontroller, with authtoken in Authorization header.

AuthorizeController inherits from controller. Anycontroller inherits from a custom base controller which performs the authorization code.

My example requires the following route which directs POST requests to an ActionResult named post in any controller. I am typing this in by hand to simplify it as much as possible to give you the general idea. Don't expect to cut and paste and have it work :)

routes.MapRoute(
    "post-object",
    "{controller}",
    new { controller = "Home", action = "post" {,
    new { httpMethod = new HttpMethodConstraint("POST")}
);

Your auth controller can use this

public class AuthorizationController : Controller
{
    public ActionResult Post()
    {
        string authBody;
        var request = ControllerContext.HttpContext.Request;
        var response = ControllerContext.HttpContext.Response;

        using(var reader = new StreamReader(request.InputStream))
            authBody = reader.ReadToEnd();

        // authorize based on credentials passed in request body
        var authToken = {result of your auth method}

        response.Write(authToken);

    }
}

Your other controllers inherit from a base controller

public class BaseController : Controller
{
    protected override void Execute(RequestContext requestContext)
    {
        var request = requestContext.HttpContext.Request;
        var response = requestContext.HttpContext.Response;

        var authToken = Request.Headers["Authorization"];

        // use token to authorize in your own method
        var authorized = AmIAuthorized();

        if(authorized = false) { 
            response.StatusCode = 401; 
            response.Write("Invalid token");
            return;            
        }

        response.StatusCode = 200; // OK

        base.Execute(requestContext);  // allow inheriting controller to continue

    }
}

Sample code to call the api

 public static void ExecutePostRequest(string contentType)
        {
            request = (HttpWebRequest)WebRequest.Create(Uri + Querystring);
            request.Method = "POST";
            request.ContentType = contentType;  // application/json usually
            request.Headers["Authorization"] = token;

            using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
                writer.Write(postRequestData);

            // GetResponse reaises an exception on http status code 400
            // We can pull response out of the exception and continue on our way            
            try
            {
                response = (HttpWebResponse)request.GetResponse();
            }
            catch (WebException ex)
            {
                response = (HttpWebResponse)ex.Response;
            }
            finally
            {
                using (StreamReader reader =
                    new StreamReader(response.GetResponseStream()))
                    responseText = reader.ReadToEnd();
                httpcontext = HttpContext.Current;
            }
        }
like image 73
Jason Watts Avatar answered Nov 15 '22 21:11

Jason Watts