C# tricks: Securing your controllers
This article is dedicated to the ExileOffice team - revolutionizing the way we run our business in Exilesoft.
As applications move more and more of their business logic to the client side, it falls upon us to simplify what’s left on the server to avoid distractions. My previous article shows how I get rid of lot of the boring and noisy code.
There is one thing that the server will always be responsible for: Security. Much functionality can be moved to the client side, but we have no way of stopping users from hacking our client-side code or faking requests.
In this article, I extend upon the generic controllers to handle the one of the most important aspects of security: Authorization.
This is not your data!
The first order of business is to ensure that only logged in users can access our controller. This is trivially done with AuthorizationAttribute:
[ApplicationAuthorization]
public class EntityController : ApiController where T : class, IEntity
{
// ... details omitted for brevity
}
public class ApplicationAuthorizationAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext context)
{
base.OnAuthorization(context);
var username = context.Request.GetUserPrincipal().Identity.Name;
context.Request.Properties["UserCompany"] =
new ApplicationDbContext().Users.Find(username).Company;
}
}
Here, our custom AuthorizationAttribute sets a property by looking for the Company of the currently logged in user in the database. You probably want to cache this in a session.
The second problem is to ensure that the user can only read and write data.
According to my previous article, we want to move as much as possible of the boilerplate logic into a generic EntityController. Here we add authorization:
[ApplicationAuthorization]
public class EntityController : ApiController where T : class, IEntity
{
private readonly ApplicationDbContext db = new ApplicationDbContext();
private DbSet DbSet
{
get { return db.Set(); }
}
public IEnumerable Entities
{
get { return DbSet.Where(AccessFilter); }
}
public virtual Func AccessFilter
{
// Default to safe
get { return (entity) => false; }
}
public T Get(int id)
{
return Entities.Single(e => e.Key == id);
}
public HttpResponseMessage Post(T entity)
{
if (!AccessFilter(entity))
{
return Request.CreateErrorResponse(HttpStatusCode.Forbidden, "No access");
}
DbSet.Add(entity);
db.SaveChanges();
return Request.CreateResponse(HttpStatusCode.Created, entity);
}
}
Here, we give the controllers a virtual AccessFilter property. Whenever we read data, we need to run it through the access filter, and whenever we write data, we need to check that the data matches the filter.
It’s up to each entity controller to determine how to match the data. Here is an example for PersonData:
public class PersonController : EntityController
{
public override Func AccessFilter
{
get { return (entity) => entity.Company == Request.Properties["UserCompany"]; }
}
public IEnumerable GetAdminsByCity(string city)
{
return Entities.WhereTypeAndCity(PersonType.Admin, city).SelectFirstAndLastName();
}
}
The clue here is to ensure that PersonController can only get at Persons through the Entities collection. This collection is always filtered with the Company that the user has access to.
The code will resolve to simply: db.Persons.Where(p => p.Company == Request.Properties["UserCompany"]).Where(p => p.Type == PersonType.Admin && p.City == city).Select(p => new { p.FirstName, p.LastName })
. By using the filtered Entities, we can ensure that we never accidentally forget to apply the users company.