A Multitenant application
Multitenancy is the concept that your application serves distinct non-overlapping accounts, with resources assigned to and belonging to each account. A simple example of this is an off-line console video game. Each game copy is bought and paid for by a single owner (“account”), and in that copy there may be some amount of gameplay saved data, development data, and user configuration which is unique and sequestered to that copy. In some cases that data may need to be synced with other copies that the user owns. For example a PC version or potentially a status update on their mobile device.
On the opposite side of the spectrum there are open communities where no data is restricted to the users, a simplified version of Twitter is a good example. Tweets are public, anyone can read them. While a user may be able to post new tweets and delete ones they posted, the environment that user sees is a combination of all users on that platform.
In these extremes, access control policies aren’t that meaningful, since users either have access to nothing (except their resources) or everything. It becomes required and thus more challenging, when resources are partially shared. Take a photo sharing application. While photos may be owned by a single account, a user may want to share their photos with their friends. Additionally they could create albums which their whole family can see, or public ones they intend to demonstrate their photography prowess.
Users create separate accounts which have their own data, own configuration, but may need to share that data with users in that account, or users in other accounts. This is at the core of what is considered a multitenant application.
Application resources
If we use a document repository example we can start to build out what the recommendations might be to secure that data. To start off we’ll look at the types of actions users may want to perform, these are often called user stories.
- A user can create an account and provision the necessary resources, add anything to that account, and additionally a user should be able to edit. They are the Owner of that account.
- An account administrator would like to be able to add additional users to the account to manage the documents that are there.
- An account manager needs to upload documents, change documents, and delete them.
- A user in an account needs to be able to share a document with anyone else, or potentially just specific people, irrespective of the account the user is part of.
This is just some of the functionality necessary to be built into a document repository. While it is trivial to save documents (depending on their size), getting the permissions right so that they are both easy to maintain but can also provide the necessary flexibility for your users to control access they want to is difficult. We’ll walk through one possible implementation below.
Modeled Actions
- Create Account - Let’s assume no restricted access is necessary here, anyone can create an account.
- Invite User to Account - Perhaps something like
- Resource: accounts/{accountId}/users
- Permissions: AddUser/InviteUser, RemoveUser, ReadUsers
- List accounts - I’ll assume accounts are private. A user can only list accounts they are part of, potentially if you share a document a user might need to be able to see some aspects about that account that owns the document:
- Resource: accounts/{accountId} and accounts/{accountId}/info
- Permissions: updateName/Description, ReadAccount
- List documents - Need to be able to list the documents in the account
- Resource: accounts/{accountId}/documents/(documentPath)
- Permissions: AddDocument, DeleteDocument, ReadDocument, EditDocument
- List users - Need to be able to list users that have access to a document
- Resource: accounts/{accountId}/documents/(documentPath)/members
- Permissions: ShareDocument, RemoveAccess, UpdateAccess, AssignDocumentOwner
These resource uris are a good match for our user stories about a document repository. For these we would create the relevant roles. So far we listed out the permissions, since the permissions are checked by services we’ll want role abstractions that contain these permissions for the resources.
Relevant Roles
- Account Admin:
- Will own everything about an account
- Permissions: ✶
- Account Manager:
- Can modify users and documents
- Permissions: AddDocument, DeleteDocument, ReadDocument, EditDocument, ShareDocument, RemoveAccess, UpdateAccess, AssignDocumentOwner
- Account Member:
- Can modify documents
- Permissions: AddDocument, DeleteDocument, ReadDocument, EditDocument, ShareDocument
- Document Viewer:
- Can read a document
- Permissions: ReadDocument, (ShareDocument)
Relevant Resources
- accounts/{accountId}/users
- accounts/{accountId}/info
- accounts/{accountId}/documents/(documentPath)
Standard Multitenant Resource Recommendations
It can be difficult to get the resource paths just right for your application. What’s important is matching up the user stories to necessary access control checks. Since Authress provides scoped permissions and resources, the best recommendation are resource uris that looks similar to the following:
NS:tenants/{tenantId}/parentResources/{parentResourceId}/resources/{resourceId}/subResources/{subResourceId}
- NS is a custom namespace, your usage of security policies might span across different product spaces, if these are to be separate, prefixing them goes a long way.
- We can separate each section of the path with hardcoded identifier. This is important so that resources of different types are easily distinguishable. If we had /{id}/{id2} it would not be possible to differentiate access to /resources/{id}/sub/{id2} and /{accounts}/{id}/resource/{id2}, since they look the same.
- Always scope with the tenant. There are some situations where resources might be shared, but someone fundamentally one tenant/account always ows the resource.
- Resources in Authress are cascading, so if there is a hierarchy relation between them, this is expressible in the resourceUri. This is a great way to automatically grant access to sub child resources when they are created without needing to create or updating access records. If a user has access to /resources/{id} then they will also have the same roles/permissions to all sub resources /resources/{id}/subResource/{sub1}
(Another example exists in the Zoom Case study)
In some cases resources are very fluid and too much scoping can be a problem, but in Authress these can be changed by updating access records and a simple migration can be used to propagate them if the access control model needs to be changed.