Example 3: Multi-tenant access control with RBAC
To elaborate on the previous RBAC example, you can expand your requirements to include SaaS multi-tenancy, which is a common requirement for SaaS providers. In multi-tenant solutions, resource access is always provided on behalf of a given tenant. That is, users of Tenant A cannot view the data of Tenant B, even if that data is logically or physically collocated in a system. The following example illustrates how you can implement tenant isolation by using multiple Verified Permissions policy stores, and how you can employ user roles to define permissions within the tenant.
Using the Per Tenant Policy Store design pattern is a best practice for
maintaining tenant isolation while implementing access control with Verified Permissions. In this
scenario, Tenant A and Tenant B user requests are verified against separate policy
stores, DATAMICROSERVICE_POLICYSTORE_A
and
DATAMICROSERVICE_POLICYSTORE_B
, respectively. For more information
about Verified Permissions design considerations for multi-tenant SaaS applications, see the Verified Permissions multi-tenant design
considerations section.

The following policy resides in the DATAMICROSERVICE_POLICYSTORE_A
policy store. It verifies that the principal will be a part of the group
allAccessRole
of type Role
. In this case, the
principal will be allowed to perform the viewData
and
updateData
actions on all resources that are associated with Tenant
A.
permit ( principal in MultitenantApp::Role::"allAccessRole", action in [ MultitenantApp::Action::"viewData", MultitenantApp::Action::"updateData" ], resource );
The following policies reside in the DATAMICROSERVICE_POLICYSTORE_B
policy store. The first policy verifies that the principal is part of the
updateDataRole
group of type Role
. Assuming that is
the case, it gives permission to principals to perform the updateData
action on resources that are associated with Tenant B.
permit ( principal in MultitenantApp::Role::"updateDataRole", action == MultitenantApp::Action::"updateData", resource );
This second policy mandates that principals that are a part of the
viewDataRole
group of type Role
should be allowed to
perform the viewData
action on resources that are associated with
Tenant B.
permit ( principal in MultitenantApp::Role::"viewDataRole", action == MultitenantApp::Action::"viewData", resource );
The authorization request made from Tenant A needs to be sent to the
DATAMICROSERVICE_POLICYSTORE_A
policy store and verified by the
policies that belong to that store. In this case, it's verified by the first policy
discussed earlier as part of this example. In this authorization request, the
principal of type User
with a value of Alice
is requesting
to perform the viewData
action. The principal
belongs to the group allAccessRole
of type Role
. Alice is
trying to perform the viewData
action on the SampleData
resource. Because Alice has the allAccessRole
role, this evaluation results in an ALLOW
decision.
{ "policyStoreId": "DATAMICROSERVICE_POLICYSTORE_A", "principal": { "entityType": "MultitenantApp::User", "entityId": "Alice" }, "action": { "actionType": "MultitenantApp::Action", "actionId": "viewData" }, "resource": { "entityType": "MultitenantApp::Data", "entityId": "SampleData" }, "entities": { "entityList": [ { "identifier": { "entityType": "MultitenantApp::User", "entityId": "Alice" }, "attributes": {}, "parents": [ { "entityType": "MultitenantApp::Role", "entityId": "allAccessRole" } ] }, { "identifier": { "entityType": "MultitenantApp::Data", "entityId": "SampleData" }, "attributes": {}, "parents": [] } ] } }
If, instead, you view a request made from Tenant B by User Bob
, you
will see something like the following authorization request. The request is sent to
the DATAMICROSERVICE_POLICYSTORE_B
policy store because it originates
from Tenant B. In this request, the principal Bob
wants to perform the
action updateData
on the resource SampleData
. However,
Bob
is not a part of a group that has access to the action
updateData
on that resource. Therefore, the request results in a
DENY
decision.
{ "policyStoreId": "DATAMICROSERVICE_POLICYSTORE_B", "principal": { "entityType": "MultitenantApp::User", "entityId": "Bob" }, "action": { "actionType": "MultitenantApp::Action", "actionId": "updateData" }, "resource": { "entityType": "MultitenantApp::Data", "entityId": "SampleData" }, "entities": { "entityList": [ { "identifier": { "entityType": "MultitenantApp::User", "entityId": "Bob" }, "attributes": {}, "parents": [ { "entityType": "MultitenantApp::Role", "entityId": "viewDataRole" } ] }, { "identifier": { "entityType": "MultitenantApp::Data", "entityId": "SampleData" }, "attributes": {}, "parents": [] } ] } }
In this third example, User Alice
tries to perform the
viewData
action on the resource SampleData
. This
request is directed to the DATAMICROSERVICE_POLICYSTORE_A
policy store
because the principal Alice
belongs to Tenant A. Alice
is
a part of the group allAccessRole
of the type Role
, which
permits her to perform the viewData
action on resources. As such, the
request results in an ALLOW
decision.
{ "policyStoreId": "DATAMICROSERVICE_POLICYSTORE_A", "principal": { "entityType": "MultitenantApp::User", "entityId": "Alice" }, "action": { "actionType": "MultitenantApp::Action", "actionId": "viewData" }, "resource": { "entityType": "MultitenantApp::Data", "entityId": "SampleData" }, "entities": { "entityList": [ { "identifier": { "entityType": "MultitenantApp::User", "entityId": "Alice" }, "attributes": {}, "parents": [ { "entityType": "MultitenantApp::Role", "entityId": "allAccessRole" } ] }, { "identifier": { "entityType": "MultitenantApp::Data", "entityId": "SampleData" }, "attributes": {}, "parents": [] } ] } }