Friday, 15 November 2013

Authentication and Authorization in ASP.NET Web API

Authorization happens later in the pipeline, closer to the controller. That lets you make more granular choices when you grant access to resources.
  • Authorization filters run before the controller action. If the request is not authorized, the filter returns an error response, and the action is not invoked.
  • Within a controller action, you can get the current principal from the ApiController.User property. For example, you might filter a list of resources based on the user name, returning only those resources that belong to that user.

Using the [Authorize] Attribute

Web API provides a built-in authorization filter, AuthorizeAttribute. This filter checks whether the user is authenticated. If not, it returns HTTP status code 401 (Unauthorized), without invoking the action.
You can apply the filter globally, at the controller level, or at the level of inidivual actions.
Globally: To restrict access for every Web API controller, add the AuthorizeAttribute filter to the global filter list:
public static void Register(HttpConfiguration config)
{
    config.Filters.Add(new AuthorizeAttribute());
}
Controller: To restrict access for a specific controller, add the filter as an attribute to the controller:
// Require authorization for all actions on the controller.
[Authorize]
public class ValuesController : ApiController
{
    public HttpResponseMessage Get(int id) { ... }
    public HttpResponseMessage Post() { ... }
}
Action: To restrict access for specific actions, add the attribute to the action method:
public class ValuesController : ApiController
{
    public HttpResponseMessage Get() { ... }

    // Require authorization for a specific action.
    [Authorize]
    public HttpResponseMessage Post() { ... }
}
Alternatively, you can restrict the controller and then allow anonymous access to specific actions, by using the[AllowAnonymous] attribute. In the following example, the Post method is restricted, but the Get method allows anonymous access.
[Authorize]
public class ValuesController : ApiController
{
    [AllowAnonymous]
    public HttpResponseMessage Get() { ... }

    public HttpResponseMessage Post() { ... }
}
In the previous examples, the filter allows any authenticated user to access the restricted methods; only anonymous users are kept out. You can also limit access to specific users or to users in specific roles:
// Restrict by user:
[Authorize(Users="Alice,Bob")]
public class ValuesController : ApiController
{
}
   
// Restrict by role:
[Authorize(Roles="Administrators")]
public class ValuesController : ApiController
{
}
The AuthorizeAttribute filter for Web API controllers is located in the System.Web.Httpnamespace. There is a similar filter for MVC controllers in the System.Web.Mvc namespace, which is not compatible with Web API controllers.

Custom Authorization Filters

To write a custom authorization filter, derive from one of these types:
  • AuthorizeAttribute. Extend this class to perform authorization logic based on the current user and the user’s roles.
  • AuthorizationFilterAttribute. Extend this class to perform synchronous authorization logic that is not necessarily based on the current user or role.
  • IAuthorizationFilter. Implement this interface to perform asynchronous authorization logic; for example, if your authorization logic makes asynchronous I/O or network calls. (If your authorization logic is CPU-bound, it is simpler to derive from AuthorizationFilterAttribute, because then you don’t need to write an asynchronous method.)
The following diagram shows the class hierarchy for the AuthorizeAttribute class.

Authorization Inside a Controller Action

In some cases, you might allow a request to proceed, but change the behavior based on the principal. For example, the information that you return might change depending on the user’s role. Within a controller method, you can get the current principle from the ApiController.User property.
public HttpResponseMessage Get()
{
    if (User.IsInRole("Administrators"))
    {
        // ...
    }
}

What are $select and $expand

The $select operator allows a client to pick a subset of the properties of an entity to be retrieved when querying a feed or a single entity. The $expand operator allows a client to retrieve related entities for a given navigation property in line with the entities being retrieved.
By using $select and $expand, we can make sure that we get the data we need in an optimal way.
For example, we could use $select to return only the Id and Name properties of an entity, and we could use $expand to retrieve a customer and its related Orders on a single query.

How to use $select and $expand in an application

Let’s see an example, we will start by retrieving only the Name property of a feed of customers. In order to do that, we need to do 3 things:
  • Create a model like the one in the following diagram:
image
  • Write a CustomersController that returns a feed of Customers or a specific Customer for a given key:
[ODataNullValue]
public class CustomersController : ODataController
{
    ShoppingContext context;

    public CustomersController()
    {
        context = new ShoppingContext();
    }

    [Queryable(MaxExpansionDepth = 5)]
    public IQueryable<Customer> Get()
    {
        return context.Customers.AsQueryable();
    }

    [Queryable(MaxExpansionDepth = 5)]
    public SingleResult<Customer> Get(int key)
    {
        return SingleResult.Create(context.Customers.Where(c => c.Id == key));
    }
}
  • Setup the server and map the OData route as in the following code:
class Program
{
    static void Main(string[] args)
    {
        string serviceUrl = "http://localhost:12345";
        using (WebApp.Start(serviceUrl, Configure))
        {
            Console.WriteLine("Server listening on {0}", serviceUrl);
            Console.ReadKey();
        }
    }

    private static void Configure(IAppBuilder builder)
    {
        HttpConfiguration configuration = new HttpConfiguration();
        IEdmModel model = ShoppingEdmModel.GetModel();
        HttpServer server = new HttpServer(configuration);
        configuration.Routes.MapODataRoute("odata", "odata", model, 
        new DefaultODataBatchHandler(server));

        builder.UseHttpMessageHandler(server);
    }
}
Once we have our server up and running, we only need to send the following request using fiddler in order to get just the Name property of the Customers feed.
GET http://localhost:12345/odata/Customers?$select=Name HTTP/1.1
Host: localhost:12345
accept: application/json
Here is the response that we get:
HTTP/1.1 200 OK
Content-Length: 415
Content-Type: application/json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
DataServiceVersion: 3.0
Date: Tue, 28 May 2013 23:58:17 GMT
{
  "odata.metadata":"http://localhost:12345/odata/$metadata#Customers","value":[
    {
      "Name":"Name 1"
    },{
      "Name":"Name 2"
    },{
      "Name":"Name 3"
    },{
      "Name":"Name 4"
    },{
      "Name":"Name 5"
    },{
      "Name":"Name 6"
    },{
      "Name":"Name 7"
    },{
      "Name":"Name 8"
    },{
      "Name":"Name 9"
    },{
      "Name":"Name 0"
    }
  ]
}
As we see on the response, we are still sending back a feed of customers, but we are only retuning their name, which allows us to improve the efficiency of our applications by reducing the amount of data returned from the database.
We can see this if we see the query that Entity Framework sends to the database when we query just for the Ids of the customers:
SELECT
[Extent1].[Id] AS [Id],
N'bf436648-6bf3-4bd2-9639-2810e0a91f53' AS [C1],
N'Id' AS [C2]
FROM [dbo].[Customers] AS [Extent1]
Aditionally, we can also apply $select when we are retrieving just a single entity. We could send the following request using fiddler:
GET http://localhost:12345/odata/Customers(5)?$select=Name HTTP/1.1
Host: localhost:12345
accept: application/json
As the following table shows, we get back a Customer, but we only get back its name property:
HTTP/1.1 200 OK
Content-Length: 100
Content-Type: application/json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
DataServiceVersion: 3.0
Date: Wed, 29 May 2013 00:01:21 GMT
{
  "odata.metadata":"http://localhost:12345/odata/$metadata#Customers/@Element","Name":"Name 5"
}
Here is a more complex example in which we use $select and $expand together:
GET http://localhost:18340/odata/Customers?$select=Id,Name,Orders/BillingAddress&$expand=Orders HTTP/1.1
accept: application/json
Host: localhost:18340