In the last two articles, I’ve gone over how you can create a basic datasync service and add authentication to the service. What if you want to do something more complex? Authorization that is an on/off switch is reasonable as a first pass, but rarely allows you to handle the cases you actually need to implement.
Introducing the Access Control Provider
To handle cases where your needs are a little more complex, we’ve introduced a new interface:
IAccessControlProvider<T>. This requires you to implement three methods:
GetDataViewis a func used to limit what data the user can see.
IsAuthorizedAsyncdetermines if the user is allowed to do the requested operation. If not, a
401 Unauthorizedresponse is returned.
PreCommitHookAsyncis called right before each database write to allow you to modify the stored entity.
A quick example
Let’s say you have the following model:
As models go, this isn’t too bad. We’ve used the
JsonIgnore to ensure that the user connecting from the mobile app never gets to see if the record is approved. However, we want our internal code to see it so we can make decisions based on the approval state.
Now, we want to provide a table controller that
- Only shows the user approved records.
- Doesn’t allow the user to modify records.
To do this, we create a new class that implements
GetDataView() is used in a LINQ
.Where(view) call to limit the data. In this case, IsApproved must be set to true to show the data. The
IsAuthorizedAsync() method returns true if the operation is a query or read operation - create, update, or delete operations are disallowed. We don’t need to do any changes to the pre-commit hook since we aren’t doing any writes.
We use async methods for the authorization and pre-commit hook because you may want to do additional database lookups to provide the right functionality.
You can use this in the controller as follows:
You can also embed the access control provider in the controller. Just implement the interface in the controller, then set
this. Implementing it as a separate class allows code re-use.
Using authentication in an access control provider
Of course, eventually, you will want a multi-user table. Start by implementing an interface for the user ID:
Now, let’s create a model with the user ID:
As is common with security information, we decorate the
JsonIgnore so it doesn’t get sent to the client. Now, let’s write a basic but reusable access control provider that allows any authenticated user to create or query the table, but limits modifications to the same user.
There is a lot going on here. Let’s start at the interface level. This class only accepts models that implement
IUserId. We accept a HttpContextAccessor that allows us to access the
HttpContext from within an async controller or middleware (more on that in a moment). Then we have our three methods. We take care in the first two methods to ensure the user is authenticated before returning “the right thing”. In the pre-commit hook, we set the
UserId to the ID of the authenticated user. At this point, we know the user is already authenticated (since the
IsAuthorizedAsync method has been called and returned true), so it’s safe to do the assignment.
To use this, we need to first declare the dependency on
Program.cs and add the line:
Then adjust the controller:
IHttpContextAccessor is passed in via depedency injection, and we use it to create the access control provider.
Let’s say I wanted to have a model that could be read by anonymous users, but only updated by authenticated users. I could do something like this:
AllowAnonymous here to ensure the user identity is populated in the
HttpContext. If you don’t do some sort of authorization, then it isn’t populated. Now, let’s take a look at the access control provider:
That’s it for authentication and authorization. But there is more to go over. There are a bunch of options you can also supply to the table controller to modify the behavior. In the next article, I’ll go over those options to round out the basic operations for a table.