Implementing ABAC
ABAC in Descope is implemented by checking custom attributes stored on users or tenants. There are three main approaches:
- Standalone ABAC: Check custom attributes directly in your application code to make authorization decisions
- ABAC with RBAC: Combine attribute checks with role-based permissions
- ABAC with ReBAC: Query users by custom attributes, then create explicit relations using the ReBAC DSL schema
Standalone ABAC: Checking Attributes in Code
The simplest way to implement ABAC is to check custom attributes directly in your application code:
// Load user and check custom attributes
const user = await descopeClient.management.user.load(loginId);
// Check subscription tier
const hasPremiumAccess = user.customAttributes?.subscriptionTier === 'premium';
// Check department
const isInEngineering = user.customAttributes?.department === 'Engineering';
// Check multiple attributes
const canAccessFeature =
user.customAttributes?.subscriptionTier === 'premium' &&
user.customAttributes?.licenseStatus === 'active';# Load user and check custom attributes
user = descope_client.mgmt.user.load(login_id)
# Check subscription tier
custom_attrs = user.custom_attributes or {}
has_premium_access = custom_attrs.get('subscriptionTier') == 'premium'
# Check department
is_in_engineering = custom_attrs.get('department') == 'Engineering'
# Check multiple attributes
can_access_feature = (
custom_attrs.get('subscriptionTier') == 'premium' and
custom_attrs.get('licenseStatus') == 'active'
)// Load user and check custom attributes
ctx := context.Background()
user, err := descopeClient.Management.User().Load(ctx, loginID)
// Check subscription tier
tier, _ := user.CustomAttributes["subscriptionTier"].(string)
hasPremiumAccess := tier == "premium"
// Check department
dept, _ := user.CustomAttributes["department"].(string)
isInEngineering := dept == "Engineering"
// Check multiple attributes
licenseStatus, _ := user.CustomAttributes["licenseStatus"].(string)
canAccessFeature := tier == "premium" && licenseStatus == "active"ABAC with RBAC: Combining Attributes with Roles
Combine ABAC attribute checks with RBAC role checks to create more granular authorization. This approach is ideal when you need role-based access with additional attribute constraints.
Approach: Check Role and Attributes
- Load the user and check their assigned roles
- Check custom attributes to add additional conditions
- Combine both checks to make the final authorization decision
Example: Department-Based Document Editing
Here's how to check if a user with the "Editor" role can edit a document in their department:
const checkCanEditDocument = async (loginId, documentId) => {
// 1. Load user
const user = await descopeClient.management.user.load(loginId);
if (!user.ok) {
return false;
}
// 2. RBAC: Check if user has Editor role
const hasEditorRole = user.roleNames.includes('Editor');
// 3. ABAC: Check if user's department matches document's department
const departmentMatch =
user.customAttributes?.department === document.department;
// 4. ABAC: Check if license is active
const hasActiveLicense =
user.customAttributes?.licenseStatus === 'active';
// 5. Combined authorization decision
return hasEditorRole && departmentMatch && hasActiveLicense;
};def check_can_edit_document(login_id, document_id):
# 1. Load user
user = descope_client.mgmt.user.load(login_id)
if not user:
return False
# 2. RBAC: Check if user has Editor role
has_editor_role = 'Editor' in user.role_names
# 3. ABAC: Check if user's department matches document's department
custom_attrs = user.custom_attributes or {}
department_match = (
custom_attrs.get('department') == document.department
)
# 4. ABAC: Check if license is active
has_active_license = (
custom_attrs.get('licenseStatus') == 'active'
)
# 5. Combined authorization decision
return has_editor_role and department_match and has_active_licensefunc checkCanEditDocument(ctx context.Context, loginID string, documentID string) bool {
// 1. Load user
user, err := descopeClient.Management.User().Load(ctx, loginID)
if err != nil {
return false
}
// 2. RBAC: Check if user has Editor role
hasEditorRole := false
for _, role := range user.RoleNames {
if role == "Editor" {
hasEditorRole = true
break
}
}
// 3. ABAC: Check if user's department matches document's department
dept, _ := user.CustomAttributes["department"].(string)
departmentMatch := dept == document.Department
// 4. ABAC: Check if license is active
licenseStatus, _ := user.CustomAttributes["licenseStatus"].(string)
hasActiveLicense := licenseStatus == "active"
// 5. Combined authorization decision
return hasEditorRole && departmentMatch && hasActiveLicense
}ABAC with ReBAC: Checking Attributes Before ReBAC
ABAC works best with ReBAC when you check attributes first, then perform the ReBAC check. This approach combines attribute-based filtering with relationship-based access without requiring you to maintain relations based on attributes.
Check out the ReBAC Docs to learn about creating a schema and relations.
Approach: Check Attributes, Then ReBAC
- Load the user and check their custom attributes
- If attributes pass, perform the ReBAC check to verify relationship-based access
- Use the DSL to define permissions and leverage relation inheritance
This approach ensures you're using the DSL schema properly while incorporating attribute-based filtering without maintenance overhead.
Example: Department-Based Access
Here's how to check if a user in a specific department can access a document:
const checkDepartmentDocumentAccess = async (loginId, documentId) => {
// 1. Load user and check department attribute
const user = await descopeClient.management.user.load(loginId);
if (!user.ok) {
return false;
}
// ABAC: Check if user is in Engineering department
if (user.customAttributes?.department !== 'Engineering') {
return false; // Attribute check failed
}
// 2. If attributes pass, check ReBAC relation
const canView = await descopeClient.management.fga.check([{
resource: documentId,
resourceType: 'doc',
relation: 'can_view',
target: user.userId,
targetType: 'user',
}]);
return canView[0].allowed;
};def check_department_document_access(login_id, document_id):
# 1. Load user and check department attribute
user = descope_client.mgmt.user.load(login_id)
if not user:
return False
# ABAC: Check if user is in Engineering department
custom_attrs = user.custom_attributes or {}
if custom_attrs.get('department') != 'Engineering':
return False # Attribute check failed
# 2. If attributes pass, check ReBAC relation
can_view = descope_client.mgmt.fga.check([
{
"resource": document_id,
"resourceType": "doc",
"relation": "can_view",
"target": user.user_id,
"targetType": "user",
}
])
return can_view[0]["allowed"]func checkDepartmentDocumentAccess(ctx context.Context, loginID string, documentID string) bool {
// 1. Load user and check department attribute
user, err := descopeClient.Management.User().Load(ctx, loginID)
if err != nil {
return false
}
// ABAC: Check if user is in Engineering department
dept, _ := user.CustomAttributes["department"].(string)
if dept != "Engineering" {
return false // Attribute check failed
}
// 2. If attributes pass, check ReBAC relation
canView, err := descopeClient.Management.FGA().Check(ctx, []*descope.FGARelation{
{
Resource: documentID,
ResourceType: "doc",
Relation: "can_view",
Target: user.UserID,
TargetType: "user",
},
})
if err != nil {
return false
}
return canView[0].Allowed
}