Implementing ABAC

ABAC in Descope is implemented by checking custom attributes stored on users or tenants. There are three main approaches:

  1. Standalone ABAC: Check custom attributes directly in your application code to make authorization decisions
  2. ABAC with RBAC: Combine attribute checks with role-based permissions
  3. 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';

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

  1. Load the user and check their assigned roles
  2. Check custom attributes to add additional conditions
  3. 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;
};

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

  1. Load the user and check their custom attributes
  2. If attributes pass, perform the ReBAC check to verify relationship-based access
  3. 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;
};
Was this helpful?