Authorize resources in a REST service
There are powerful and flexible authorization mechanisms in the ASP.NET world. For example, ASP.NET Core 2.0 provides the developer with the ability to use authorization policies , handlers , etc.
But how to implement a GET method that returns a list of resources? And if this method also should not return all resources, but only a specified page? Each user should see only those resources to which he has access. You can get a complete list from the database each time and then filter it based on the rights of the current user, but it will be too inefficient - the amount of resources can be very large. It is preferable to resolve authorization and pagination issues at the database query level.
This article describes an approach to solving the authorization problem in a ASP.NET Web API 2-based REST service using the Entity Framework.
Suppose we are developing a site that allows you to post various resources, for example, text documents. We have a REST service that performs CRUD operations on these documents. The authentication task, that is, determining the authenticity of the user, has already been solved. Users in our system can have different roles. We assume that we have two types of users: administrators and ordinary users.
Now we are faced with the task of authorization - giving the user rights to perform certain actions on documents. We want each user who posted the document to then be able to dynamically control the access of other users to this document.
So, users are divided into two types: administrators and regular users. Administrators have maximum rights to access any document, ordinary users have maximum rights to their documents and the rights granted to others. We assume that there are three permissions: read, write (change) and delete the document: Read , Write and Delete . Each subsequent authority includes the previous one, i.e. Write includes Read , and Delete includes Write and Read .
First of all, we need to add a new table to the database to store permissions.

Here is the objectidIs the identifier of the resource, ObjectType is the type of resource, UserId is the Id of the user, and finally Permission is the permission.
Add the necessary definitions:
When adding a new resource , a record with maximum rights for the user who created the resource should appear in the Permissions table . The easiest way to do this is with a DB trigger. We assume that in the Documents table there are columns Id (document identifier) and CreatedBy (identifier of the user who created the document). Add a new trigger to the Documents table :
Thus, we will automatically have the Delete permission for the creator of the document.
You can also add a delete trigger:
At first glance, it seems that storing administrator rights in the database is redundant, since the administrator already has full rights to any document. What happens if administrator privileges are removed in the client-side rights editor? - for the administrator, nothing will change. There is a temptation to process administrator rights in a special way, for example, not to add an entry to the database or not to show its rights in the editor.
Nevertheless, it is better to use a general approach. What happens if an administrator suddenly ceases to be an administrator and becomes a regular user?
We use the Entity Framework. The classes and interfaces of the data model look something like this:
Introducing the IModel interface may be useful for unit testing if we need test data:
Also pay attention to the body of the constructor MyDbContext . The Database.Log = x => Trace.WriteLine (x) line allows you to see real SQL queries in the Visual Studio Output window during debugging.
Create the IAccessor interface:
The GetQuery method will return the IQueriable interface for fetching resources, in our case, documents that are readable by the current user. The GetPermission method will return the current user's authority to the specified resource. HasPermission method added for convenience. It answers the question of whether the current user has the specified right to the specified resource.
The IAuthorizedObject interface defines the resource that we are going to authorize. This interface is very simple and contains only the resource Id:
The Document class will need to be inherited from the IAuthorizedObject interface :
It's time to implement specific IAccessor interface implementations . We will have two implementations: Administrator and User . First, add the base class UserBase :
UserBase will be useful to us when implementing the Administrator and User classes . In the constructor, he initializes his members in order to be able to implement generalized methods. The Query method returns a dataset from the DB context by the given type, ObjectType returns the native value of the enumeration by type, and GetPermission returns the privilege by the given user and object identifiers for the generic type.
Now we can start creating the classes Administrator and User . As for the administrator, everything is simple here, since the administrator has full rights to all documents:
With the User class, everything is much more interesting: the GetQuery method should only return documents that the user has access to. Therefore, we must consider the credentials of this user. We implement this in one query to the database, i.e. We’ll do something that, in fact, is what started.
It is understood that in this way, new user roles can be easily introduced. Suppose we need to add an “advanced user” who has the right to read all documents created by other users. It is clear that to implement the corresponding class is a simple task.
I will give an example of such a class:
Finally, you need a class to create specific implementations of the IAccessor interface . It will look something like this:
Now that we have all the necessary infrastructure, we can easily implement a document controller:
Next, we need to add a controller for CRUD rights operations for a specific document. There will be nothing special in it, except that each method will have to take into account the rights of the current user on this document.
If we assume that we have a DocumentPermissionService class that takes over operations on permissions and unloads the controller, the code will look like this:
Note that the GetPermissions method requires Write permission . At first glance, it seems that a user who has the right to read a document should be able to get all the permissions for this document. However, it is not. In accordance with the principle of minimum privileges, we should not give the user privileges that are not necessary for him. A user with Read permission does not have the ability to change the rights of users to the document, respectively, he does not need data on the existing rights.
Everything is changing. We may have new requirements and business rules. How adaptive is our approach to changing requirements? Let's try to imagine what could change in the future.
The first thing that comes to mind is the addition of new types of resources. Everything looks good here: if we add a new entity to the DB model, say Image , we just need to add a new ObjectType enumeration value and one line of code to the constructor of the UserBase class :
A little harder with users. Suppose we need to add the ability to group users and assign rights to groups. Can we make changes to the project relatively painlessly?
The first thing to do is add a new AccountType column to the Permissions table . It would also be nice to rename UserId to AccountId , since now this column will store either the user Id or group Id depending on the value of AccountType .
We'll have to change the GetQuery methods in the IAccessor interface implementations .. Now it will be necessary to take into account the user's membership in the group and check the group’s credentials in addition to the user's own credentials.
But overall, such a change in functionality does not look critical.
But how to implement a GET method that returns a list of resources? And if this method also should not return all resources, but only a specified page? Each user should see only those resources to which he has access. You can get a complete list from the database each time and then filter it based on the rights of the current user, but it will be too inefficient - the amount of resources can be very large. It is preferable to resolve authorization and pagination issues at the database query level.
This article describes an approach to solving the authorization problem in a ASP.NET Web API 2-based REST service using the Entity Framework.
Task
Suppose we are developing a site that allows you to post various resources, for example, text documents. We have a REST service that performs CRUD operations on these documents. The authentication task, that is, determining the authenticity of the user, has already been solved. Users in our system can have different roles. We assume that we have two types of users: administrators and ordinary users.
Now we are faced with the task of authorization - giving the user rights to perform certain actions on documents. We want each user who posted the document to then be able to dynamically control the access of other users to this document.
Getting started
So, users are divided into two types: administrators and regular users. Administrators have maximum rights to access any document, ordinary users have maximum rights to their documents and the rights granted to others. We assume that there are three permissions: read, write (change) and delete the document: Read , Write and Delete . Each subsequent authority includes the previous one, i.e. Write includes Read , and Delete includes Write and Read .
First of all, we need to add a new table to the database to store permissions.

Here is the objectidIs the identifier of the resource, ObjectType is the type of resource, UserId is the Id of the user, and finally Permission is the permission.
Add the necessary definitions:
public enum ObjectType
{
// May grow in the future
Document
}
public enum Permission
{
None = 0,
Read = 1,
Write = 2,
Delete = 3
}
public enum Role
{
Administrator,
User
}
When adding a new resource , a record with maximum rights for the user who created the resource should appear in the Permissions table . The easiest way to do this is with a DB trigger. We assume that in the Documents table there are columns Id (document identifier) and CreatedBy (identifier of the user who created the document). Add a new trigger to the Documents table :
CREATE TRIGGER [dbo].[TR_Documents_Insert] ON [dbo].[Documents] FOR INSERT
AS
BEGIN
INSERT INTO Permissions(ObjectId, ObjectType, UserId, Permission)
SELECT inserted.Id,
1, -- ObjectType.Document
inserted.CreatedBy,
3 -- Permission.Delete
FROM inserted
END
Thus, we will automatically have the Delete permission for the creator of the document.
You can also add a delete trigger:
CREATE TRIGGER [dbo].[TR_Documents_Delete] on [dbo].[Documents] FOR DELETE
AS
BEGIN
DELETE FROM Permissions
WHERE ObjectId IN (SELECT ID FROM deleted) AND ObjectType = 1
END
At first glance, it seems that storing administrator rights in the database is redundant, since the administrator already has full rights to any document. What happens if administrator privileges are removed in the client-side rights editor? - for the administrator, nothing will change. There is a temptation to process administrator rights in a special way, for example, not to add an entry to the database or not to show its rights in the editor.
Nevertheless, it is better to use a general approach. What happens if an administrator suddenly ceases to be an administrator and becomes a regular user?
Model
We use the Entity Framework. The classes and interfaces of the data model look something like this:
public class Document
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public int CreatedBy { get; set; }
public string Source { get; set; }
}
public class UserPermission
{
[Key]
[Column(Order = 1)]
public long ObjectId { get; set; }
[Key]
[Column(Order = 2)]
public byte ObjectType { get; set; }
[Key]
[Column(Order = 3)]
public int UserId { get; set; }
public byte Permission { get; set; }
}
public interface IModel
{
IQueryable Documents { get; }
IQueryable Permissions { get; }
}
public class MyDbContext : DbContext, IModel
{
public MyDbContext()
{
}
public MyDbContext(string connectString)
: base(connectString)
{
#if DEBUG
Database.Log = x => Trace.WriteLine(x);
#endif
}
public DbSet Documents { get; set; }
public DbSet Permissions { get; set; }
#region Explicit IModel interface implementations
IQueryable IModel.Documents => Documents;
IQueryable IModel.Permissions => Permissions;
#endregion
}
Introducing the IModel interface may be useful for unit testing if we need test data:
internal class DbContextStub : IModel
{
public List Documents { get; } =
new List();
public List Permissions { get; } =
new List();
#region Explicit Interface Implementations
IQueryable IModel.Documents => Documents.AsQueryable();
IQueryable IModel.Permissions => Permissions.AsQueryable();
#endregion
}
Also pay attention to the body of the constructor MyDbContext . The Database.Log = x => Trace.WriteLine (x) line allows you to see real SQL queries in the Visual Studio Output window during debugging.
Classes for authorization
Create the IAccessor interface:
public interface IAccessor
{
IQueryable GetQuery() where T : class, IAuthorizedObject;
Permission GetPermission(long objectId)
where T : class, IAuthorizedObject;
bool HasPermission(long objectId, Permission permission)
where T : class, IAuthorizedObject;
}
The GetQuery method will return the IQueriable interface for fetching resources, in our case, documents that are readable by the current user. The GetPermission method will return the current user's authority to the specified resource. HasPermission method added for convenience. It answers the question of whether the current user has the specified right to the specified resource.
The IAuthorizedObject interface defines the resource that we are going to authorize. This interface is very simple and contains only the resource Id:
public interface IAuthorizedObject
{
long Id { get; }
}
The Document class will need to be inherited from the IAuthorizedObject interface :
public class Document : IAuthorizedObject
It's time to implement specific IAccessor interface implementations . We will have two implementations: Administrator and User . First, add the base class UserBase :
public abstract class UserBase : IAccessor
{
protected readonly IModel Model;
protected readonly int Id;
private readonly Dictionary _typeToQuery =
new Dictionary();
private readonly Dictionary _typeToEnum =
new Dictionary();
protected UserBase(IModel model, int userId)
{
Model = model;
Id = userId;
AppendAuthorizedObject(Auth.ObjectType.Document, Model.Documents);
// Append new authorized objects here...
}
private void AppendAuthorizedObject(ObjectType type, IQueryable source)
where T : class, IAuthorizedObject
{
_typeToQuery.Add(typeof(T), source);
_typeToEnum.Add(typeof(T), type);
}
protected IQueryable Query() where T : class, IAuthorizedObject
{
IQueryable query;
if (!_typeToQuery.TryGetValue(typeof(T), out query))
throw new InvalidOperationException(
$"Unsupported object type {typeof(T)}");
return query as IQueryable;
}
protected byte ObjectType() where T : class, IAuthorizedObject
{
ObjectType type;
if (!_typeToEnum.TryGetValue(typeof(T), out type))
throw new InvalidOperationException(
$"Unsupported object type {typeof(T)}");
return (byte)type;
}
protected Permission GetPermission(int userId, long objectId)
where T : class, IAuthorizedObject
{
var entities = Query();
var objectType = ObjectType();
var query =
from obj in entities
from p in Model.Permissions
where
p.ObjectType == objectType && p.ObjectId == objectId &&
obj.Id == p.ObjectId &&
p.UserId == userId
select p.Permission;
return (Permission) query.FirstOrDefault();
}
public abstract IQueryable GetQuery()
where T : class, IAuthorizedObject;
public abstract Permission GetPermission(long objectId)
where T : class, IAuthorizedObject;
public abstract bool HasPermission(long objectId, Permission permission)
where T : class, IAuthorizedObject;
}
UserBase will be useful to us when implementing the Administrator and User classes . In the constructor, he initializes his members in order to be able to implement generalized methods. The Query method returns a dataset from the DB context by the given type, ObjectType returns the native value of the enumeration by type, and GetPermission returns the privilege by the given user and object identifiers for the generic type.
Now we can start creating the classes Administrator and User . As for the administrator, everything is simple here, since the administrator has full rights to all documents:
public class Administrator : UserBase
{
public Administrator(IModel model, int userId)
: base(model, userId)
{
}
public override IQueryable GetQuery()
{
return Query();
}
public override bool HasPermission(long objectId, Permission permission)
{
return permission != Permission.None;
}
public override Permission GetPermission(long objectId)
{
return Permission.Delete;
}
}
With the User class, everything is much more interesting: the GetQuery method should only return documents that the user has access to. Therefore, we must consider the credentials of this user. We implement this in one query to the database, i.e. We’ll do something that, in fact, is what started.
public class User : UserBase
{
public User(IModel model, int userId)
: base(model, userId)
{
}
public override IQueryable GetQuery()
{
var entities = Query();
var objectType = ObjectType();
return
from obj in entities
from p in Model.Permissions
where
p.ObjectType == objectType && p.UserId == Id &&
obj.Id == p.ObjectId
select obj;
}
public override bool HasPermission(long objectId, Permission permission)
{
return permission == Permission.None
? GetPermission(objectId) == Permission.None
: GetPermission(objectId) >= permission;
}
public override Permission GetPermission(long objectId)
{
return GetPermission(Id, objectId);
}
}
It is understood that in this way, new user roles can be easily introduced. Suppose we need to add an “advanced user” who has the right to read all documents created by other users. It is clear that to implement the corresponding class is a simple task.
I will give an example of such a class:
public class AdvancedUser : UserBase
{
public AdvancedUser(IModel model, int userId)
: base(model, userId)
{
}
public override IQueryable GetQuery()
{
// Advanced user can see all resources
return Query();
}
public override bool HasPermission(long objectId, Permission permission)
{
if (permission == Permission.None)
return false;
return GetPermission(objectId) >= permission;
}
public override Permission GetPermission(long objectId)
{
// Return own permission if exists or Permission.Read
return Max(GetPermission(Id, objectId), Permission.Read);
}
private static Permission Max(Permission perm1, Permission perm2)
{
return (Permission) Math.Max((int) perm1, (int) perm2);
}
}
Finally, you need a class to create specific implementations of the IAccessor interface . It will look something like this:
public static class Factory
{
public static IAccessor CreateAccessor(IPrincipal principal, IModel model)
{
if( IsAdministrator(principal))
return new Administrator(model, GetUserId(principal));
else
return new User(model, GetUserId(principal));
}
private static bool IsAdministrator(IPrincipal principal)
{
return principal.IsInRole("SYSTEM_ADMINISTRATE");
}
private static int GetUserId(IPrincipal principal)
{
var id = 0; // TODO: Obtain user id from Thread.CurrentPrincipal here...
return id;
}
}
DocumentController
Now that we have all the necessary infrastructure, we can easily implement a document controller:
[RoutePrefix("documents")]
public class DocumentsController : ApiController
{
private readonly MyDbContext _db = new MyDbContext();
private IAccessor Accessor =>
Factory.CreateAccessor(Thread.CurrentPrincipal, _db);
[HttpGet]
[Route("", Name = "GetDocuments")]
[ResponseType(typeof(IQueryable))]
public IHttpActionResult GetDocuments()
{
var query = Accessor.GetQuery();
return Ok(query);
}
[HttpGet]
[Route("{id:long}", Name = "GetDocumentById")]
[ResponseType(typeof(Document))]
public IHttpActionResult GetDocumentById(long id)
{
if (!Accessor.HasPermission(id, Permission.Read))
return NotFound();
var document = _db.Documents.FirstOrDefault(e => e.Id == id);
if (document == null)
return NotFound();
return Ok(document);
}
[HttpPost]
[Route("", Name = "CreateDocument")]
[ResponseType(typeof(Document))]
public IHttpActionResult CreateDocument(Document document)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
_db.Documents.Add(document);
_db.SaveChanges();
return CreatedAtRoute("CreateDocument", new { id = document.Id }, document);
}
[HttpDelete]
[Route("{id:long}", Name = "DeleteDocument")]
[ResponseType(typeof(Document))]
public IHttpActionResult DeleteDocument(long id)
{
if (Accessor.HasPermission(id, Permission.Delete))
return NotFound();
var document = _db.Documents.FirstOrDefault(e => e.Id == id);
if (document == null)
return NotFound();
_db.Documents.Remove(document);
_db.SaveChanges();
return Ok(document);
}
protected override void Dispose(bool disposing)
{
if (disposing)
_db.Dispose();
base.Dispose(disposing);
}
}
DocumentPermissionController
Next, we need to add a controller for CRUD rights operations for a specific document. There will be nothing special in it, except that each method will have to take into account the rights of the current user on this document.
If we assume that we have a DocumentPermissionService class that takes over operations on permissions and unloads the controller, the code will look like this:
[RoutePrefix("documents")]
public class DocumentPermissionsController : ApiController
{
private readonly MyDbContext _db = new MyDbContext();
private readonly DocumentPermissionService _service =
new DocumentPermissionService();
private IAccessor Accessor =>
Factory.CreateAccessor(Thread.CurrentPrincipal, _db);
[HttpGet]
[Route("{id:long}/permissions", Name = "GetPermissions")]
[ResponseType(typeof(IQueryable))]
public IHttpActionResult GetPermissions(long id)
{
if (!Accessor.HasPermission(id, Permission.Write))
return NotFound();
var permissions = _service.GetPermissions(id);
return Ok(permissions);
}
[HttpPatch]
[Route("{id:long}/permissions", Name = "SetPermissions")]
public HttpResponseMessage SetPermissions(
long id, IList permissions)
{
if (!Accessor.HasPermission(id, Permission.Write))
return Request.CreateResponse(HttpStatusCode.NotFound);
string err;
var validationCode = _service.ValidatePermissions(permissions, out err);
if (validationCode != HttpStatusCode.OK)
return Request.CreateResponse(validationCode, err);
_service.SetPermissions(id, permissions);
return Request.CreateResponse(HttpStatusCode.OK);
}
[Route("{id:long}/permissions/{userId:int}", Name = "DeletePermission")]
[HttpDelete]
public IHttpActionResult DeletePermission(long id, int userId)
{
if (!Accessor.HasPermission(id, Permission.Write))
return NotFound();
var isDeleted = _service.DeletePermission(id, userId);
return isDeleted ? (IHttpActionResult) Ok() : NotFound();
}
protected override void Dispose(bool disposing)
{
if (disposing)
_db.Dispose();
base.Dispose(disposing);
}
}
Note that the GetPermissions method requires Write permission . At first glance, it seems that a user who has the right to read a document should be able to get all the permissions for this document. However, it is not. In accordance with the principle of minimum privileges, we should not give the user privileges that are not necessary for him. A user with Read permission does not have the ability to change the rights of users to the document, respectively, he does not need data on the existing rights.
Extensibility
Everything is changing. We may have new requirements and business rules. How adaptive is our approach to changing requirements? Let's try to imagine what could change in the future.
The first thing that comes to mind is the addition of new types of resources. Everything looks good here: if we add a new entity to the DB model, say Image , we just need to add a new ObjectType enumeration value and one line of code to the constructor of the UserBase class :
AppendAuthorizedObject(ObjectType.Image, Model.Image);
A little harder with users. Suppose we need to add the ability to group users and assign rights to groups. Can we make changes to the project relatively painlessly?
The first thing to do is add a new AccountType column to the Permissions table . It would also be nice to rename UserId to AccountId , since now this column will store either the user Id or group Id depending on the value of AccountType .
We'll have to change the GetQuery methods in the IAccessor interface implementations .. Now it will be necessary to take into account the user's membership in the group and check the group’s credentials in addition to the user's own credentials.
But overall, such a change in functionality does not look critical.