npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

abac-engine

v1.1.0

Published

True Attribute-Based Access Control (ABAC) engine for Node.js - Authorization decisions based purely on attributes

Readme

ABAC Engine for Node.js

A powerful, zero-dependency Attribute-Based Access Control (ABAC) engine for Node.js and TypeScript. Make authorization decisions based on attributes instead of roles.

What is ABAC?

For Beginners

Traditional access control uses roles (like "admin", "user", "manager"). This works until:

  • You need fine-grained permissions ("can edit their own documents")
  • Context matters ("only during business hours")
  • Requirements change frequently

ABAC uses attributes instead. Think of it as asking questions:

  • "Is this user's department the same as the document's department?"
  • "Is the user's clearance level higher than the resource's classification?"
  • "Is it currently between 9 AM and 5 PM?"

For Experts

ABAC follows the XACML architecture with four key components:

  • PDP (Policy Decision Point): The engine that evaluates policies
  • PIP (Policy Information Point): Attribute providers that fetch attributes
  • PAP (Policy Administration Point): Your policy storage (Prisma, files, etc.)
  • PEP (Policy Enforcement Point): Middleware that enforces decisions

This package implements the PDP and provides optional PIP implementations.

Installation

npm install abac-engine

Quick Start

import {
  ABACEngine,
  PolicyBuilder,
  AttributeRef,
  CombiningAlgorithm
} from 'abac-engine';

// 1. Create a policy
const policy = PolicyBuilder.create('document-access')
  .version('1.0.0')
  .permit()
  .description('Users can edit their own documents')
  .condition(
    // Subject's id must equal resource's ownerId
    ConditionBuilder.equals(
      AttributeRef.subject('id'),
      AttributeRef.resource('ownerId')
    )
  )
  .build();

// 2. Create the engine
const engine = new ABACEngine({
  combiningAlgorithm: CombiningAlgorithm.DenyOverrides
});

// 3. Make an authorization request
const request = {
  subject: {
    id: 'user-123',
    attributes: { department: 'Engineering' }
  },
  resource: {
    id: 'doc-456',
    type: 'document',
    attributes: { ownerId: 'user-123', department: 'Engineering' }
  },
  action: {
    id: 'edit'
  }
};

// 4. Evaluate
const decision = await engine.evaluate(request, [policy]);

if (decision.decision === Decision.Permit) {
  console.log('Access granted!');
} else {
  console.log('Access denied');
}

Core Concepts Explained

1. Request

A request contains all the information needed to make an authorization decision:

interface ABACRequest {
  subject: Subject; // Who is making the request?
  resource: Resource; // What are they trying to access?
  action: Action; // What are they trying to do?
  environment?: Environment; // What's the context?
}

Example:

const request = {
  subject: {
    id: 'alice',
    attributes: {
      department: 'Engineering',
      role: 'developer',
      clearanceLevel: 3
    }
  },
  resource: {
    id: 'database-prod',
    type: 'database',
    attributes: {
      environment: 'production',
      classification: 2
    }
  },
  action: {
    id: 'write'
  },
  environment: {
    currentTime: new Date(),
    ipAddress: '192.168.1.100'
  }
};

2. Policy

A policy is a rule that grants or denies access based on conditions:

interface ABACPolicy {
  id: string; // Unique identifier
  version: string; // Policy version
  effect: Effect; // Permit or Deny
  description?: string; // Human-readable description
  condition?: Condition; // When does this policy apply?
  obligations?: Obligation[]; // What must happen if policy matches?
  advice?: Advice[]; // Optional suggestions
}

Example:

const policy = {
  id: 'eng-dept-access',
  version: '1.0.0',
  effect: Effect.Permit,
  description: 'Engineering department members can read engineering documents',
  condition: {
    operator: LogicalOperator.And,
    conditions: [
      {
        operator: ComparisonOperator.Equals,
        left: { category: 'subject', attributeId: 'department' },
        right: 'Engineering'
      },
      {
        operator: ComparisonOperator.Equals,
        left: { category: 'resource', attributeId: 'department' },
        right: 'Engineering'
      }
    ]
  }
};

3. Conditions

Conditions determine when a policy applies. Three types:

Comparison Conditions

Compare two values:

// subject.clearanceLevel > resource.classification
ConditionBuilder.greaterThan(
  AttributeRef.subject('clearanceLevel'),
  AttributeRef.resource('classification')
);

Available Operators:

  • equals, notEquals
  • greaterThan, greaterThanOrEqual
  • lessThan, lessThanOrEqual
  • in, notIn
  • contains, startsWith, endsWith
  • matchesRegex
  • exists, notExists

Logical Conditions

Combine multiple conditions:

// (department === 'Engineering') AND (level > 3)
ConditionBuilder.equals(AttributeRef.subject('department'), 'Engineering').and(
  ConditionBuilder.greaterThan(AttributeRef.subject('level'), 3)
);

// (role === 'admin') OR (isOwner === true)
ConditionBuilder.equals(AttributeRef.subject('role'), 'admin').or(
  ConditionBuilder.equals(AttributeRef.subject('isOwner'), true)
);

// NOT (status === 'suspended')
ConditionBuilder.equals(AttributeRef.subject('status'), 'suspended').not();

Function Conditions

Use custom logic:

// Register a custom function
engine.registerFunction('is_business_hours', () => {
  const hour = new Date().getHours();
  return hour >= 9 && hour <= 17;
});

// Use in policy
const policy = PolicyBuilder.create('business-hours-only')
  .permit()
  .condition(ConditionBuilder.function('is_business_hours'))
  .build();

4. Combining Algorithms

When multiple policies apply, how do we decide? That's what combining algorithms do:

DenyOverrides (recommended)

  • If ANY policy denies, the result is Deny
  • If at least one permits and none deny, result is Permit
  • Use when security is critical

PermitOverrides

  • If ANY policy permits, the result is Permit
  • If at least one denies and none permit, result is Deny
  • Use when availability is more important than security

FirstApplicable

  • Use the first policy that matches
  • Order matters!

OnlyOneApplicable

  • Only one policy should match
  • Returns Indeterminate if multiple match

DenyUnlessPermit

  • Default to Deny unless explicitly permitted

PermitUnlessDeny

  • Default to Permit unless explicitly denied
const engine = new ABACEngine({
  combiningAlgorithm: CombiningAlgorithm.DenyOverrides
});

5. Attribute Providers

Attribute providers fetch attributes dynamically during evaluation:

import {
  InMemoryAttributeProvider,
  EnvironmentAttributeProvider
} from 'abac-engine';

// In-memory provider for subjects/resources
const subjectProvider = new InMemoryAttributeProvider('subject', 'users');
subjectProvider.setAttribute('user-123', 'department', 'Engineering');
subjectProvider.setAttribute('user-123', 'level', 5);

// Environment provider for context (time, IP, etc.)
const envProvider = new EnvironmentAttributeProvider();

const engine = new ABACEngine({
  combiningAlgorithm: CombiningAlgorithm.DenyOverrides,
  attributeProviders: [subjectProvider, envProvider]
});

Built-in Providers:

  • InMemoryAttributeProvider - Store attributes in memory
  • EnvironmentAttributeProvider - Automatic context (time, IP, etc.)
  • DatabaseAttributeProvider - Fetch from database
  • RestApiAttributeProvider - Fetch from REST API
  • LdapAttributeProvider - Fetch from LDAP
  • CachedAttributeProvider - Add caching to any provider
  • CompositeAttributeProvider - Combine multiple providers

Policy Storage

You manage policy storage using your preferred method. The engine just evaluates policies - giving you complete flexibility in how you store and manage them (PAP).

Loading Policies

From JSON Files

import {
  loadPoliciesFromFile,
  loadAndValidatePoliciesFromFile
} from 'abac-engine';

// Basic load
const policies = await loadPoliciesFromFile('./policies.json');

// Load with automatic validation
const { policies, validationResults } =
  await loadAndValidatePoliciesFromFile('./policies.json');
// Throws ValidationError if any policy is invalid

From JSON Strings

import { loadPoliciesFromJSON } from 'abac-engine';

const jsonString =
  '[{"id": "policy-1", "version": "1.0.0", "effect": "Permit", ...}]';
const policies = loadPoliciesFromJSON(jsonString);

Saving Policies

To JSON Files

import {
  savePolicyToFile,
  savePoliciesToFile,
  saveAndValidatePolicyToFile,
  saveAndValidatePoliciesToFile
} from 'abac-engine';

// Save a single policy
const policy = PolicyBuilder.create('my-policy')
  .permit()
  .condition(
    ConditionBuilder.equals(Attributes.subject.id, Attributes.resource.owner)
  )
  .build();

await savePolicyToFile(policy, './policies/my-policy.json');

// Save multiple policies
const policies = [policy1, policy2, policy3];
await savePoliciesToFile(policies, './policies/all-policies.json');

// Save with automatic validation (throws if invalid)
await saveAndValidatePolicyToFile(policy, './policies/validated-policy.json');
await saveAndValidatePoliciesToFile(
  policies,
  './policies/validated-policies.json'
);

Export to JSON Strings

import { exportPolicyToJSON, exportPoliciesToJSON } from 'abac-engine';

// Export single policy (pretty-printed by default)
const policyJson = exportPolicyToJSON(policy);
console.log(policyJson);

// Export without pretty-printing (compact)
const compactJson = exportPolicyToJSON(policy, false);

// Export multiple policies
const policiesJson = exportPoliciesToJSON([policy1, policy2, policy3]);

With Database (Prisma)

// schema.prisma
model AbacPolicy {
  id          String   @id
  version     String
  effect      String
  description String?
  condition   Json?
  createdAt   DateTime @default(now())
}

// Your code
import { validatePolicy } from 'abac-engine';

// Load policies
const policies = await prisma.abacPolicy.findMany();

// Save with validation
async function savePolicy(policy: ABACPolicy) {
  const validation = validatePolicy(policy);
  if (!validation.valid) {
    throw new Error(validation.errors.map(e => e.message).join(', '));
  }
  await prisma.abacPolicy.create({ data: policy });
}

// Load policies from database
const dbPolicies = await prisma.abacPolicy.findMany();

// Evaluate
const decision = await engine.evaluate(request, dbPolicies);

Policy Persistence Patterns

Version Control for Policies

import {
  savePoliciesToFile,
  loadAndValidatePoliciesFromFile
} from 'abac-engine';

// Save policies to version-controlled file
const policies = [
  PolicyPatterns.ownership(['read', 'update']),
  PolicyPatterns.departmentAccess(['read'], ['public', 'internal'])
];

await savePoliciesToFile(policies, './config/policies.json');
// Commit to git for versioning and review

Hot Reload Policies

import { loadPoliciesFromFile } from 'abac-engine';
import { watch } from 'fs';

let currentPolicies: ABACPolicy[] = [];

async function reloadPolicies() {
  currentPolicies = await loadPoliciesFromFile('./policies.json');
  console.log(`Loaded ${currentPolicies.length} policies`);
}

// Initial load
await reloadPolicies();

// Watch for changes
watch('./policies.json', async () => {
  await reloadPolicies();
});

Migration: Export from Database to Files

import { savePoliciesToFile } from 'abac-engine';

// Export policies from database to file system
async function exportPolicies() {
  const policies = await prisma.abacPolicy.findMany();
  await savePoliciesToFile(policies, './backup/policies.json', true);
  console.log(`Exported ${policies.length} policies`);
}

await exportPolicies();

Import Policies into Database

import { loadAndValidatePoliciesFromFile } from 'abac-engine';

async function importPolicies() {
  const { policies } = await loadAndValidatePoliciesFromFile('./policies.json');

  for (const policy of policies) {
    await prisma.abacPolicy.upsert({
      where: { id: policy.id },
      update: policy,
      create: policy
    });
  }

  console.log(`Imported ${policies.length} policies`);
}

await importPolicies();

With Caching

import { PolicyCache } from 'abac-engine';

const cache = new PolicyCache(300); // 5 minutes TTL

async function getPolicies() {
  return await cache.get(async () => {
    return await prisma.abacPolicy.findMany();
  });
}

const policies = await getPolicies(); // Loads from DB
const policies2 = await getPolicies(); // Uses cache

// Invalidate when policies change
cache.invalidate();

// Example: Cache with file-based policies
const fileCache = new PolicyCache(60); // 1 minute TTL

async function getCachedPolicies() {
  return await fileCache.get(async () => {
    return await loadPoliciesFromFile('./policies.json');
  });
}

Real-World Examples

Example 1: Document Management System

const policies = [
  // Owners can do anything with their documents
  PolicyBuilder.create('owner-full-access')
    .permit()
    .description('Document owners have full access')
    .condition(
      ConditionBuilder.equals(
        AttributeRef.subject('id'),
        AttributeRef.resource('ownerId')
      )
    )
    .build(),

  // Same department members can read
  PolicyBuilder.create('dept-read-access')
    .permit()
    .description('Department members can read department documents')
    .condition(
      ConditionBuilder.equals(
        AttributeRef.subject('department'),
        AttributeRef.resource('department')
      ).and(ConditionBuilder.equals(AttributeRef.action('id'), 'read'))
    )
    .build(),

  // Admins can do everything
  PolicyBuilder.create('admin-access')
    .permit()
    .description('Admins have full access')
    .condition(ConditionBuilder.equals(AttributeRef.subject('role'), 'admin'))
    .build()
];

Example 2: Multi-Tenant SaaS

// Tenant isolation policy
const tenantIsolation = PolicyBuilder.create('tenant-isolation')
  .deny()
  .description('Users cannot access other tenants resources')
  .condition(
    ConditionBuilder.notEquals(
      AttributeRef.subject('tenantId'),
      AttributeRef.resource('tenantId')
    )
  )
  .build();

// Usage
const request = {
  subject: {
    id: 'user-1',
    attributes: { tenantId: 'tenant-a' }
  },
  resource: {
    id: 'resource-1',
    type: 'data',
    attributes: { tenantId: 'tenant-b' } // Different tenant!
  },
  action: { id: 'read' }
};

const decision = await engine.evaluate(request, [tenantIsolation]);
// Result: Deny

Example 3: Healthcare System

// HIPAA-compliant access control
const policies = [
  // Doctors can access their patients' records
  PolicyBuilder.create('doctor-patient-access')
    .permit()
    .condition(
      ConditionBuilder.equals(AttributeRef.subject('role'), 'doctor').and(
        ConditionBuilder.in(
          AttributeRef.resource('patientId'),
          AttributeRef.subject('assignedPatients')
        )
      )
    )
    .build(),

  // Emergency access (break-glass)
  PolicyBuilder.create('emergency-access')
    .permit()
    .description('Emergency access with audit logging')
    .condition(
      ConditionBuilder.equals(AttributeRef.subject('emergencyMode'), true)
    )
    .logObligation({
      level: 'critical',
      message: 'Emergency access used',
      timestamp: new Date()
    })
    .build()
];

Example 4: Time-Based Access

// Register custom time function
engine.registerFunction('is_business_hours', () => {
  const hour = new Date().getHours();
  const day = new Date().getDay();
  return day >= 1 && day <= 5 && hour >= 9 && hour <= 17;
});

const policy = PolicyBuilder.create('business-hours-only')
  .permit()
  .description('Certain operations only allowed during business hours')
  .condition(
    ConditionBuilder.function('is_business_hours').and(
      ConditionBuilder.equals(AttributeRef.action('id'), 'deploy')
    )
  )
  .build();

API Reference

ABACEngine

class ABACEngine {
  constructor(config: ABACEngineConfig);

  evaluate(request: ABACRequest, policies: ABACPolicy[]): Promise<ABACDecision>;

  registerFunction(name: string, fn: ConditionFunction): void;

  getMetrics(): EvaluationMetrics;
  getAuditLog(): ABACAccessLog[];
}

PolicyBuilder

PolicyBuilder.create(id: string)
  .version(version: string)
  .permit() | .deny()
  .description(description: string)
  .condition(condition: Condition | ConditionBuilder)
  .target(target: PolicyTarget | TargetBuilder)
  .logObligation(params: Record<string, unknown>)
  .notifyObligation(params: Record<string, unknown>)
  .build(): ABACPolicy

ConditionBuilder

// Comparison
ConditionBuilder.equals(left, right);
ConditionBuilder.notEquals(left, right);
ConditionBuilder.greaterThan(left, right);
ConditionBuilder.lessThan(left, right);
ConditionBuilder.in(value, array);
ConditionBuilder.contains(haystack, needle);
ConditionBuilder.exists(attribute);

// Logical
condition.and(otherCondition);
condition.or(otherCondition);
condition.not();

// Function
ConditionBuilder.function(name, ...args);

AttributeRef

AttributeRef.subject(attributeId: string)
AttributeRef.resource(attributeId: string)
AttributeRef.action(attributeId: string)
AttributeRef.environment(attributeId: string)

Validation

import { validatePolicy, validatePolicies } from 'abac-engine';

// Validate single policy
const result = validatePolicy(policy);
if (!result.valid) {
  console.error(result.errors);
}

// Validate multiple
const results = validatePolicies(policies);

// Validate and throw
validatePolicyOrThrow(policy); // Throws if invalid

Advanced Features

Obligations and Advice

Obligations are actions that MUST happen if a policy matches:

const policy = PolicyBuilder.create('audit-sensitive-access')
  .permit()
  .condition(...)
  .logObligation({
    level: 'warning',
    message: 'Sensitive data accessed',
    userId: AttributeRef.subject('id')
  })
  .notifyObligation({
    recipient: '[email protected]',
    subject: 'Sensitive Access Alert'
  })
  .build();

Advice are optional suggestions:

const policy = PolicyBuilder.create('risky-operation')
  .permit()
  .condition(...)
  .advice([{
    id: 'mfa-recommendation',
    type: 'custom',
    parameters: {
      message: 'Consider requiring MFA for this operation'
    }
  }])
  .build();

Performance Optimization

import { filterPoliciesByTarget, groupPoliciesByEffect } from 'abac-engine';

// Pre-filter policies before evaluation
const relevantPolicies = filterPoliciesByTarget(allPolicies, {
  resourceType: 'document',
  actionId: 'read'
});

// Group by effect for faster processing
const { permit, deny } = groupPoliciesByEffect(policies);

// Use caching
const cache = new PolicyCache(300);

Custom Attribute Providers

class CustomDatabaseProvider extends BaseAttributeProvider {
  constructor(private db: Database) {
    super('subject', 'custom-users');
  }

  async getAttributes(id: string): Promise<Record<string, AttributeValue>> {
    const user = await this.db.users.findOne({ id });
    return {
      department: user.department,
      role: user.role,
      permissions: user.permissions
    };
  }

  supportsAttribute(attributeId: string): boolean {
    return ['department', 'role', 'permissions'].includes(attributeId);
  }
}

Logging

The ABAC Engine supports pluggable logging for debugging and monitoring. By default, it uses a SilentLogger that doesn't output anything, making it production-safe without configuration.

Using the Default Console Logger

import { ABACEngine, ConsoleLogger, LogLevel } from 'abac-engine';

const engine = new ABACEngine({
  combiningAlgorithm: CombiningAlgorithm.DenyOverrides,
  logger: new ConsoleLogger(LogLevel.Warn) // Only log warnings and errors
});

Available Log Levels:

  • LogLevel.Debug - All messages
  • LogLevel.Info - Info, warnings, and errors
  • LogLevel.Warn - Warnings and errors only
  • LogLevel.Error - Errors only
  • LogLevel.None - No logging

Using a Custom Logger

Integrate with your existing logging solution (Winston, Pino, Bunyan, etc.):

import { ILogger } from 'abac-engine';
import winston from 'winston';

class WinstonLogger implements ILogger {
  private logger = winston.createLogger({
    level: 'info',
    format: winston.format.json(),
    transports: [new winston.transports.File({ filename: 'abac.log' })]
  });

  debug(message: string, meta?: Record<string, unknown>): void {
    this.logger.debug(message, meta);
  }

  info(message: string, meta?: Record<string, unknown>): void {
    this.logger.info(message, meta);
  }

  warn(message: string, meta?: Record<string, unknown>): void {
    this.logger.warn(message, meta);
  }

  error(
    message: string,
    error?: Error | unknown,
    meta?: Record<string, unknown>
  ): void {
    this.logger.error(message, { error, ...meta });
  }
}

const engine = new ABACEngine({
  combiningAlgorithm: CombiningAlgorithm.DenyOverrides,
  logger: new WinstonLogger()
});

Logging in Attribute Providers

Attribute providers also support logging:

import {
  InMemoryAttributeProvider,
  ConsoleLogger,
  LogLevel
} from 'abac-engine';

const logger = new ConsoleLogger(LogLevel.Debug);

const subjectProvider = new InMemoryAttributeProvider(
  'subject',
  'users',
  { user123: { department: 'Engineering' } },
  logger // Pass the same logger for consistency
);

const engine = new ABACEngine({
  combiningAlgorithm: CombiningAlgorithm.DenyOverrides,
  attributeProviders: [subjectProvider],
  logger // Use the same logger instance across components
});

What Gets Logged:

  • Attribute provider errors (failed database queries, API calls, etc.)
  • Policy applicability errors
  • Evaluation warnings and errors

Testing

Testing is simple - just pass arrays of policies:

import { ABACEngine, PolicyBuilder, Decision } from 'abac-engine';

describe('Authorization', () => {
  it('should allow owners to edit', async () => {
    const policy = PolicyBuilder.create('owner-edit')
      .permit()
      .condition(
        ConditionBuilder.equals(
          AttributeRef.subject('id'),
          AttributeRef.resource('ownerId')
        )
      )
      .build();

    const engine = new ABACEngine({
      combiningAlgorithm: CombiningAlgorithm.DenyOverrides
    });

    const request = {
      subject: { id: 'alice', attributes: {} },
      resource: {
        id: 'doc',
        type: 'document',
        attributes: { ownerId: 'alice' }
      },
      action: { id: 'edit' }
    };

    const decision = await engine.evaluate(request, [policy]);
    expect(decision.decision).toBe(Decision.Permit);
  });
});

Glossary

  • ABAC: Attribute-Based Access Control - authorization based on attributes
  • Attribute: A property of a subject, resource, action, or environment
  • Combining Algorithm: How to resolve conflicts when multiple policies apply
  • Condition: Boolean expression that determines if a policy applies
  • Decision: Result of evaluation (Permit, Deny, NotApplicable, Indeterminate)
  • Effect: What a policy does if it matches (Permit or Deny)
  • Obligation: Action that MUST happen if a policy matches
  • Advice: Optional suggestion from a policy
  • PAP: Policy Administration Point - where policies are managed (your database)
  • PDP: Policy Decision Point - the evaluation engine (this library)
  • PEP: Policy Enforcement Point - enforces decisions (your middleware)
  • PIP: Policy Information Point - provides attributes (attribute providers)
  • Policy: A rule that grants or denies access
  • Request: The authorization question being asked
  • Subject: Who is requesting access (user, service, etc.)
  • Resource: What is being accessed (document, API, database, etc.)
  • Action: What operation is being performed (read, write, delete, etc.)
  • Target: Optional filter to determine if policy applies

TypeScript Support

Fully typed with TypeScript:

import type {
  ABACPolicy,
  ABACRequest,
  ABACDecision,
  Condition,
  Effect,
  Decision
} from 'abac-engine';

Performance

  • Zero dependencies
  • Synchronous condition evaluation where possible
  • Built-in caching support
  • Efficient policy matching
  • Benchmarks: ~10,000 evaluations/second (simple policies)

📚 Documentation

Complete documentation is available in the /docs directory:

Core Documentation

Key Concepts Explained

Need clarification on specific terms?

Quick Links by Use Case

License

MIT

Contributing

Issues and PRs welcome on GitHub!

Resources


Ready to get started? Install now: npm install abac-engine

Need help? Check the Glossary for terminology or Examples for real-world use cases.