Implementing ABAC (Attribute-Based Access Control) for Web Applications

We often run into situations where RBAC is bursting at the seams. On one project, after RBAC implementation we received 47 role change requests per month—each requiring approval. ABAC cut that to 2 requests. Rules emerge like "a user can edit a document if they are its author, the document is in dra

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1281
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1237
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    977
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1025
  • image_website-sbh_0.webp
    Website development for SBH Partners
    1103
  • image_website-_0.webp
    Website development for Red Pear
    550

We often run into situations where RBAC is bursting at the seams. On one project, after RBAC implementation we received 47 role change requests per month—each requiring approval. ABAC cut that to 2 requests. Rules emerge like "a user can edit a document if they are its author, the document is in draft status, and the user works in the same organization as the document." A role alone can't handle that—context is needed. ABAC (Attribute-Based Access Control) makes decisions based on attributes of the subject (user), object (resource), and environment (time, IP, request context). Our experience shows that a hybrid RBAC+ABAC approach provides the optimal balance of performance and flexibility. We guarantee decision transparency with detailed audit.

How the Model Works

Four entities in ABAC:

  • Subject – user and their attributes: role, department, clearance_level, org_id.
  • Resource – object and its attributes: owner_id, status, org_id, classification, region.
  • Action – read, write, delete, approve.
  • Environment – time_of_day, ip_address, request_method.

A policy is a predicate over these attributes. For example:

ALLOW IF subject.org_id == resource.org_id AND (subject.role == 'editor' OR subject.id == resource.owner_id) AND resource.status IN ('draft', 'review') AND action == 'write' 

Why ABAC Is Better Than RBAC?

RBAC is simple and fast, but does not scale for complex rules. ABAC can express almost any business constraint: "a manager can approve a request if the amount < 10000 and the request was created in their department." In RBAC, you would need to introduce a new role. ABAC handles this declaratively. Let's compare key parameters:

Criteria RBAC ABAC
Flexibility Low (fixed roles) High (attributes)
Context support No Yes
Performance High Medium (with many policies)
Implementation complexity Low Medium

Policy Storage Schema

Policies can be stored in code (suitable for a small number of rules) or in a database using a DSL. Here's a variant with PostgreSQL storing conditions as JSON:

CREATE TABLE abac_policies ( id SERIAL PRIMARY KEY, name VARCHAR(128) NOT NULL, description TEXT, effect VARCHAR(8) NOT NULL CHECK (effect IN ('allow', 'deny')), priority INT NOT NULL DEFAULT 0, conditions JSONB NOT NULL, -- condition tree actions TEXT[] NOT NULL, resources TEXT[] NOT NULL -- glob: 'documents', 'documents/*' ); -- Example entry INSERT INTO abac_policies (name, effect, priority, conditions, actions, resources) VALUES ( 'editors_can_write_own_draft', 'allow', 10, '{ "operator": "AND", "conditions": [ {"attribute": "subject.role", "op": "in", "value": ["editor", "senior_editor"]}, {"attribute": "subject.org_id", "op": "eq", "value": {"ref": "resource.org_id"}}, {"attribute": "resource.status", "op": "in", "value": ["draft", "review"]} ] }', ARRAY['write', 'delete'], ARRAY['documents', 'documents/*'] ); 

Decision Engine

class ABACEngine { constructor(policies) { // Policies preloaded and sorted by priority (deny > allow on conflict) this.policies = policies.sort((a, b) => { if (a.effect === 'deny' && b.effect !== 'deny') return -1; return b.priority - a.priority; }); } evaluate(subject, resource, action, environment = {}) { const context = { subject, resource, action, environment }; for (const policy of this.policies) { if (!policy.actions.includes(action)) continue; if (!this.matchesResource(policy.resources, resource.type)) continue; if (this.evaluateCondition(policy.conditions, context)) { return policy.effect === 'allow'; } } return false; // default deny } evaluateCondition(condition, ctx) { if (condition.operator === 'AND') { return condition.conditions.every(c => this.evaluateCondition(c, ctx)); } if (condition.operator === 'OR') { return condition.conditions.some(c => this.evaluateCondition(c, ctx)); } if (condition.operator === 'NOT') { return !this.evaluateCondition(condition.condition, ctx); } // Leaf node const leftVal = this.resolveAttribute(condition.attribute, ctx); const rightVal = condition.value?.ref ? this.resolveAttribute(condition.value.ref, ctx) : condition.value; switch (condition.op) { case 'eq': return leftVal === rightVal; case 'neq': return leftVal !== rightVal; case 'in': return Array.isArray(rightVal) && rightVal.includes(leftVal); case 'gte': return leftVal >= rightVal; case 'lte': return leftVal <= rightVal; case 'contains': return Array.isArray(leftVal) && leftVal.includes(rightVal); default: return false; } } resolveAttribute(path, ctx) { // 'subject.org_id' → ctx.subject.org_id return path.split('.').reduce((obj, key) => obj?.[key], ctx); } matchesResource(patterns, resourceType) { return patterns.some(p => p === resourceType || (p.endsWith('/*') && resourceType.startsWith(p.slice(0, -2))) ); } } 

How to Integrate ABAC in Express?

const engine = new ABACEngine(await loadPoliciesFromDB()); // Reload policies on change (no server restart) db.on('policy_changed', async () => { engine.updatePolicies(await loadPoliciesFromDB()); }); function abac(action) { return async (req, res, next) => { const resource = await loadResource(req); // load object with all attributes const allowed = engine.evaluate( req.user, // subject resource, // resource action, // action { // environment ip: req.ip, timestamp: Date.now(), userAgent: req.headers['user-agent'], } ); if (!allowed) { return res.status(403).json({ error: 'Forbidden' }); } req.resource = resource; next(); }; } router.put('/documents/:id', authenticate, abac('write'), updateDocument); router.delete('/documents/:id', authenticate, abac('delete'), deleteDocument); 

Audit Log

ABAC without audit is a blind tool. Every engine decision is logged:

CREATE TABLE abac_audit_log ( id BIGSERIAL PRIMARY KEY, ts TIMESTAMPTZ NOT NULL DEFAULT now(), subject_id INT NOT NULL, resource_type VARCHAR(128), resource_id VARCHAR(128), action VARCHAR(64) NOT NULL, decision BOOLEAN NOT NULL, matched_policy_id INT REFERENCES abac_policies(id), context_snapshot JSONB -- snapshot of subject+resource attrs at decision time ); CREATE INDEX idx_abac_audit_subject ON abac_audit_log (subject_id, ts DESC); CREATE INDEX idx_abac_audit_resource ON abac_audit_log (resource_type, resource_id, ts DESC); 

This answers the question "why couldn't user X do Y with object Z three days ago?" — without it, incident investigation becomes guesswork.

Combining with RBAC

Pure ABAC is slower than RBAC under many policies—each check goes through all rules. In practice, a hybrid is used: RBAC as the first layer (fast coarse check by role), ABAC as the second (fine-grained contextual rules only where needed).

async function authorize(user, resource, action) { // Fast RBAC-check: does the role have any access to this resource type? if (!await rbac.canAccessResourceType(user.role, resource.type)) { return false; // cut off without loading object and traversing ABAC policies } // Fine-grained check via ABAC return engine.evaluate(user, resource, action); } 

How to Assess Project Complexity?

Timelines depend on the number of policies and architecture. We distinguish three options:

Option Timeline What's included
Basic 3–4 days Engine in code, 5–10 policies, tests
Advanced 7–10 days Policies in DB, REST API, UI for editing, audit
Integration with OPA 2–3 days Sidecar deployment, writing Rego policies, testing

What's Included in ABAC Implementation?

  • Analysis of access model and attributes of users, resources, environment.
  • Design and documentation of access policies.
  • Development of decision engine (or integration with OPA/Casbin).
  • Implementation of REST API for policy management.
  • Creation of audit system with log visualization.
  • Test coverage (unit + integration).
  • Deployment and monitoring setup.
  • Team training: how to write and debug policies.
  • Code warranty and post-deployment support.

Basic engine with policies stored in code — 3–4 days. Engine with policies in DB and UI for editing — 7–10 days. Adding audit log with UI — another 2–3 days. Integration with a third-party PDP (Open Policy Agent, Casbin) instead of a custom engine — 2–3 days for integration plus time to write policies in Rego or PERM.

Open Policy Agent is a mature alternative to a custom engine. Policies are written in Rego, OPA runs as a sidecar or separate service, and the application communicates via HTTP or gRPC. This adds operational complexity, but provides policy versioning, hot reload, and built-in audit.

If you have complex access requirements, contact us for an audit. We'll assess your project for free. Get in touch to discuss details. Order a turnkey ABAC implementation.