Implementing data security within application can be done with various access control strategies. The access control determines if a given user is allowed to perform a particular action on a requested resource. Do they have access to see the data, make updates, or delete it? The answer to that lies within your access control policies. There are different ways to implement security each with its own benefits and detractors. Finding the right one for your application can be the difference between happy users with secure, private data, and a disastrous data breach.
Depending on the types of applications or services your product has, different models may be more or less viable. Below I’ll discuss the prominent types as well as their benefits and detractors. For each of these it helps to have a concrete situation in mind. While it may not completely mirror your use case, an example better shows the difference.
For our example we’ll use a document repository. There are many documents. Each document can have any number of users and each of those users may have different permissions to that document. Users will share documents and change other users’ permissions as well as create, modify, and delete documents. We’ll assume there are two services, one that stores text documents and another than stores complex binary data, text and binary.
Extensible: 👎 Difficulty: ⭕️
With IAP, we’ll be storing the list of users and their permissions with the document. That means when creating a text document, the list is saved in the text document, and when creating a binary document, the list is saved in the binary document. Each service validates access and permissions to its own documents. IAP is access controlled architecture where the access policies are persisted with the document themselves. This is a very simple approach that allows for quick implementation at the cost of flexibility and scale. In the case a third type of document repository service is created, it is free to implement access control how it makes sense for those documents
- The architectural design is simple, so once it is taken care, only the implementation is necessary
- Implementation is straightforward
- Since the architecture is simple, it is easy to reason about
- Access controls are saved in the same database which means the service exposing that data can retrieve both the document and permissions at the same time. A separate database isn’t likely necessary.
- Duplicated implementation from scratch. Best case is to copy the implementation from another repository to use.
- No standard on optimizing access, no consistency for users. Accessing permissions via an API requires separate requests to multiple services. Those services might not have consistent APIs.
- Permissions cannot be shared across services
- Each service must create dedicated APIs to update and mutate the permissions
- Coupled access prevents changing permissions independent of changing the document since they are saved in the same database
- Users don’t have the ability to understand what documents they have access to across services, because access policies are distributed
- Cannot not execute complex access patterns based on user attributes or granular conditions
Extensible: 🤷 Difficulty: ⚠️
Role-based access control separates the responsibility of permissions from the documents. Roles are created and assigned to users. These assignments are stored in a separate service, so that any document application or service can access the role data. Checking user roles requires either your document service to make an api call to the role service or the list of roles is exposed via the user’s authentication token. Because the roles apply to the user only, permissions to individual documents do not exist in this paradigm. A user has access to all documents of type X or none of them at all.
- Roles can be stored in your authentication service, every document repository can consume the roles from the tokens generated by the identity provider
- Scales with the creation of additional document repositories
- Reviewing the user in the identity provider reveals the full scope of roles the user has.
- There is no de facto standard on the management of roles. Some patterns exists however.
- No ability to see what documents a user has access to given their role
- No way to grant granular access to documents.
- Permissions are attached to the access token, so role changes require expiry of previous token (~1 hour or longer).
- Roles may grant access to all documents of every type.
- Roles are ambiguous as to what they actually mean and give access to
- Users can only be given a finite number of roles due to storage patterns. As the role number increases so does bandwidth overhead of every request throughout your application. This is because the token contains all the roles, and that token must be reused for every call.
- Service clients cannot act on behalf of users, async interactions do not work as once the token expires so do the permissions contained in that token.
Extensible: 👎 Difficulty: ⭕️
Access-Controlled lists flip responsibility around work from the document-first perspective. On a document you define an ACL (which can inherit lists from other places) which specifically allow/deny user actions on the document. Because these only apply to the document, it has the opposite problems from RBAC, most notably lack of understanding of what documents a user has access to. Given the lack of complexity and the fact all the policy has to be applied to the document in one place. The flexibility is extremely lacking.
- Easily see the complete set of permissions for a document.
- ACL is stored in one place and can be edited inline
- Provides better control than RBAC to documents
- No ability to identify which users have access to a document, unless they are specifically identified in the ACL.
- The max ACL is extremely limited in size.
- The larger the ACL the longer it takes to check permission for a document.
- Duplicating a document requires duplicating the ACL, and therefore no way to keep them in sync.
- ACLs are difficult to visualize and therefore manage
Extensible: 👍 Implementation: ⛔️
Attribute-based access control removes the notions of roles and documents as far as access is concerned. Instead we’ll completely rely on attributes attached to the user and the document. Permission is granted if all the document & user attributes evaluated result in a success. In practice this means converting every authorization request to a user attribute list combined with a document attribute list and then using a policy evaluator. This offers advantages over the native RBAC paradigm, and improvements over ACLs.
- Write first class rules to define access
- Rules are ubiquitous so granular access is available
- Control permissions over more documents at the same time compare to the one-document ACLs
- Impossible to quantify who has specific access to all documents with particular classifications. Because this requires getting a full list of every potential user. Users are resolvable to attributes, but not the other way around.
- No understanding of resources, which means it is impossible to fetch all text documents a user has access to.
- No understanding of users, therefore no ability to give access to a specific user or get all users that fit a pattern.
Extensible: 👍 Difficulty: ⚠️
Dynamic access control removes all the restrictions applied above by creating distributed access policies. Fundamentally this is similar to RBAC, but the key difference is the scale and granularity for which it applies. Policies are stored separately from users, documents, and roles, and instead link all three. A policy contains one or more users, one or more roles, and one or more documents. This triad of responsibility combines the benefits of the other strategies by focusing on access control as a first class notion.
- Get all the users which have a permission to a document
- Get all the documents a user has access to.
- Dynamically update user permissions via access policies in real time.
- The triad of user, role, resource creates a de facto standard of the combination of the three lists.
- No limits on the number of users, resources, roles, or policies that can be maintained.
- Create service clients that impersonate access policies for long running actions.
- Roles and policies are shareable across documents, one policy can specify both binary and text documents, or any kind of future document.
- Decouples documents from users or roles, allowing clear separation of responsibilities.
- Flexible to migrate and change policies as resources, users, roles all change.
- Getting all the policies applied to a document becomes challenging.
- Need a separated provider to store policies and evaluate permissions indices.
There are many strategies to implement access control for documents, and already we see some of them lack the flexibility to scale with changes to our document repository. Documents aren’t even that challenging of a concept, so with more complex resources, finding the right fit for your product becomes even more important. Pick the one that best resembles the future possible access patterns for your product resources.
And while there are caveats to each of these strategies, you can add functionality on top to help compensate for what they are lacking. However this can be a real challenge. For example moving RBAC away from the authentication identity provider to a separate service. Separating these aspects provides for a much better architecture. This better resembles how the other strategies work, but starts to lose the benefits that RBAC had provided. Fundamentally, these changes to the paradigms to improve them, makes them something different, and rather than attempting to patch a system that doesn’t fit, it may be better to pick a better access strategy.