Example 4: Multi-tenant access control with RBAC and ABAC
To enhance the RBAC example in the previous section, you can add attributes to
users to create a RBAC-ABAC hybrid approach for multi-tenant access control. This
example includes the same roles from the previous example, but adds the user
attribute account_lockout_flag
and the context parameter
uses_mfa
. The example also takes a different approach to
implementing multi-tenant access control by using both RBAC and ABAC, and uses one
shared policy store instead of a different policy store for each tenant.

This example represents a multi-tenant SaaS solution in which you need to provide authorization decisions for Tenant A and Tenant B, similar to the previous example.
To implement the user lock feature, the example adds the attribute
account_lockout_flag
to the User
entity principal in
the authorization request. This flag locks user access to the system and will
DENY
all privileges to the locked out user. The
account_lockout_flag
attribute is associated with the
User
entity and is in effect for the User
until the
flag is actively revoked across multiple sessions. The example uses the
when
condition to evaluate
account_lockout_flag
.
The example also adds details about the request and session. The context
information specifies that the session has been authenticated by using multi-factor
authentication. To implement this validation, the example uses the when
condition to evaluate the uses_mfa
flag as part of the context field.
For more information about best practices for adding context, see the Cedar
documentation
permit ( principal in MultitenantApp::Role::"allAccessRole", action in [ MultitenantApp::Action::"viewData", MultitenantApp::Action::"updateData" ], resource ) when { principal.account_lockout_flag == false && context.uses_mfa == true && resource in principal.Tenant };
This policy prevents access to resources unless the resource is in the same group
as the requesting principal's Tenant
attribute. This approach to
maintaining tenant isolation is referred to as the One Shared Multi-Tenant
Policy Store approach. For more information about Verified Permissions design
considerations for multi-tenant SaaS applications, see the Verified Permissions multi-tenant design
considerations section.
The policy also ensures that the principal is a member of
allAccessRole
and restricts actions to viewData
and
updateData
. Additionally, this policy verifies that
account_lockout_flag
is false
and that the context
value for uses_mfa
evaluates to true
.
Similarly, the following policy ensures that both the principal and resource are
associated with the same tenant, which prevents cross-tenant access. This policy
also ensures that the principal is a member of viewDataRole
and
restricts actions to viewData
. Additionally, it verifies that the
account_lockout_flag
is false
and that the context
value for uses_mfa
evaluates to true
.
permit ( principal in MultitenantApp::Role::"viewDataRole", action == MultitenantApp::Action::"viewData", resource ) when { principal.account_lockout_flag == false && context.uses_mfa == true && resource in principal.Tenant };
The third policy is similar to the previous one. The policy requires the resource
to be a member of the group that corresponds to the entity that's represented by
principal.Tenant
. This ensures that both the principal and resource
are associated with Tenant B, which prevents cross-tenant access. This policy
ensures that the principal is a member of updateDataRole
and restricts
actions to updateData
. Additionally, this policy verifies that the
account_lockout_flag
is false
and that the context
value for uses_mfa
evaluates to true
.
permit ( principal in MultitenantApp::Role::"updateDataRole", action == MultitenantApp::Action::"updateData", resource ) when { principal.account_lockout_flag == false && context.uses_mfa == true && resource in principal.Tenant };
The following authorization request is evaluated by the three policies discussed
earlier in this section. In this authorization request, the principal of type
User
and with a value of Alice
makes an
updateData
request with the role allAccessRole
.
Alice
has the attribute Tenant
whose value is
Tenant::"TenantA"
. The action Alice
is trying to
perform is updateData,
and the resource it will be applied to is
SampleData
of the type Data
. SampleData
has TenantA
as a parent entity.
According to the first policy in the
<DATAMICROSERVICE_POLICYSTOREID>
policy store,
Alice
can perform the updateData
action on the
resource, assuming that the conditions in the when
clause of the policy
are met. The first condition requires the principal.Tenant
attribute to
evaluate to TenantA
. The second condition requires the principal's
attribute account_lockout_flag
to be false
. The final
condition requires the context uses_mfa
to be true
.
Because all three conditions are met, the request returns an ALLOW
decision.
{ "policyStoreId": "DATAMICROSERVICE_POLICYSTORE", "principal": { "entityType": "MultitenantApp::User", "entityId": "Alice" }, "action": { "actionType": "MultitenantApp::Action", "actionId": "updateData" }, "resource": { "entityType": "MultitenantApp::Data", "entityId": "SampleData" }, "context": { "contextMap": { "uses_mfa": { "boolean": true } } }, "entities": { "entityList": [ { "identifier": { "entityType": "MultitenantApp::User", "entityId": "Alice" }, "attributes": { { "account_lockout_flag": { "boolean": false }, "Tenant": { "entityIdentifier": { "entityType":"MultitenantApp::Tenant", "entityId":"TenantA" } } } }, "parents": [ { "entityType": "MultitenantApp::Role", "entityId": "allAccessRole" } ] }, { "identifier": { "entityType": "MultitenantApp::Data", "entityId": "SampleData" }, "attributes": {}, "parents": [ { "entityType": "MultitenantApp::Tenant", "entityId": "TenantA" } ] } ] } }