Skip to main content

Policies

ABAC (Attribute-Based Access Control) policies add a conditional layer on top of the standard RBAC system. A policy is a rule attached to a specific permission that must pass before the permission is granted, even if the user holds that permission via a role.

Policies are evaluated after RBAC passes. If RBAC denies the permission, policies are never evaluated.

How Policies Work

text
User requests "posts:delete"
  → RBAC: does the user hold posts:delete or a matching wildcard?
      No  → denied (policies never run)
      Yes → are there active policies for posts:delete?
              No  → allowed (pure RBAC)
              Yes → evaluate all active policies (AND logic)
                      All pass → allowed
                      Any fails → denied

This means policies can only restrict, never expand. A user must first have the permission via RBAC. Policies then apply additional conditions such as attribute checks, time restrictions, or IP filtering.

Policy Fields

FieldTypeDescription
idUUIDUnique policy identifier
application_idUUIDApplication this policy belongs to
permission_idUUIDThe permission this policy applies to
namestringHuman-readable policy name
descriptionstringOptional description
expressionstringPolicy condition (JSON object or expression string)
expression_typestringjson or expression
on_missing_attrstringdeny (default) or permit when an attribute is absent
is_activebooleanInactive policies are not evaluated
priorityintegerHigher priority policies are evaluated first
created_atISO 8601Creation timestamp

Expression Types

A structured JSON object using predefined operators. Safer to store and validate, and offers rich operator support.

Supported operators:

OperatorDescription
andAll child conditions must pass
orAt least one child condition must pass
notInverts a condition
eqEqual
neqNot equal
gt / gteGreater than / greater than or equal
lt / lteLess than / less than or equal
inValue is in a list
not_inValue is not in a list
containsString or array contains a value
starts_with / ends_withString prefix/suffix check
matchesRegex match (max 200-char pattern, limited backtracking)
not_null / is_nullAttribute presence check
time_betweenCurrent time is within a range (uses context.time)
day_of_weekCurrent day matches a list (uses context.day_of_week)
ip_in_cidrIP address falls within a CIDR range

Attribute namespaces in JSON expressions:

Values that begin with user., resource., or context. are treated as attribute references and resolved from the runtime attribute map. Any other string is a literal value.

Expression

A string expression using Symfony ExpressionLanguage syntax. Attributes are available as user, resource, and context variable bags.

text
user.department == 'engineering' and resource.status != 'archived'

The constant() function and all filesystem, shell, and network functions are disabled in expression-type policies for security. Expressions are length-limited to 2048 characters.

Attribute Map

At evaluation time, the attribute map is built from three sources:

NamespaceSource
user.*User attributes stored in YorAuth (set via the user attributes API)
resource.*Key-value pairs passed in the resource field of the auth check request
context.*Key-value pairs passed in the context field of the auth check request

Context defaults are injected automatically if not provided:

  • context.time — current time in HH:MM format
  • context.day_of_week — current day in lowercase (e.g., monday)

Policy Examples

Owner-only delete

Allow posts:delete only if the requesting user is the resource owner:

json
{
  "eq": ["user.id", "resource.owner_id"]
}

Business hours restriction

Allow reports:export only during business hours on weekdays:

json
{
  "and": [
    { "time_between": ["09:00", "17:00"] },
    { "day_of_week": ["monday", "tuesday", "wednesday", "thursday", "friday"] }
  ]
}

IP allowlist

Restrict admin:access to a known office network:

json
{
  "ip_in_cidr": ["context.ip", "203.0.113.0/24"]
}

Department-based access

Only allow salaries:view for users in the finance department:

json
{
  "eq": ["user.department", "finance"]
}

Combined conditions

Allow documents:publish only for senior users in specific roles publishing non-draft content:

json
{
  "and": [
    { "eq": ["user.seniority", "senior"] },
    { "neq": ["resource.status", "draft"] },
    { "in": ["user.department", ["editorial", "publishing"]] }
  ]
}

Policy API

Policies are managed via the dashboard API. The SDK bearer token for dashboard calls is Sanctum-based (cookie auth from the admin UI), not the application JWT.

List Policies

text
GET /api/dashboard/applications/{applicationId}/policies

Query parameters:

ParameterDescription
permission_idFilter by a specific permission UUID
per_pageResults per page (max 100)

Response 200 OK:

json
{
  "data": [
    {
      "id": "018e5f3a-aaaa-7000-8000-000000000001",
      "application_id": "your-application-id",
      "permission_id": "018e5f3a-1111-7000-8000-000000000001",
      "name": "Owner-only delete",
      "description": null,
      "expression": "{\"eq\":[\"user.id\",\"resource.owner_id\"]}",
      "expression_type": "json",
      "on_missing_attr": "deny",
      "is_active": true,
      "priority": 0,
      "permission": {
        "id": "018e5f3a-1111-7000-8000-000000000001",
        "name": "posts:delete",
        "resource": "posts",
        "action": "delete"
      },
      "created_at": "2026-02-25T14:30:00+00:00",
      "updated_at": "2026-02-25T14:30:00+00:00"
    }
  ],
  "meta": { "current_page": 1, "last_page": 1, "per_page": 15, "total": 1 }
}

Create a Policy

text
POST /api/dashboard/applications/{applicationId}/policies

Request body:

json
{
  "name": "Owner-only delete",
  "permission_id": "018e5f3a-1111-7000-8000-000000000001",
  "expression": "{\"eq\":[\"user.id\",\"resource.owner_id\"]}",
  "expression_type": "json",
  "on_missing_attr": "deny",
  "is_active": true,
  "priority": 0,
  "description": "Only the resource owner can delete"
}

Response 201 Created: Returns the created policy object.

Error responses:

  • 422 Unprocessable Entity — Invalid expression syntax
    json
    { "error": { "code": "INVALID_POLICY_EXPRESSION", "message": "Unknown operator: bad_op" } }
    
  • 402 Payment Required — ABAC policy limit reached for your plan

Update a Policy

text
PUT /api/dashboard/applications/{applicationId}/policies/{policyId}

All fields are optional. Set is_active: false to disable a policy without deleting it.

Request body:

json
{
  "is_active": false
}

Response 200 OK: Returns the updated policy object.

Delete a Policy

text
DELETE /api/dashboard/applications/{applicationId}/policies/{policyId}

Response 204 No Content on success.

Simulate a Policy

Test a policy expression against sample attributes without affecting real authorization decisions.

text
POST /api/dashboard/applications/{applicationId}/policies/{policyId}/simulate

Request body:

json
{
  "user_attributes": {
    "id": "user-123",
    "department": "finance"
  },
  "resource": {
    "owner_id": "user-123",
    "status": "published"
  },
  "context": {
    "ip": "203.0.113.42",
    "time": "14:30"
  }
}

Response 200 OK:

json
{
  "data": {
    "passed": true,
    "details": {
      "expression_type": "json",
      "on_missing_attr": "deny",
      "attributes_provided": [
        "user.id",
        "user.department",
        "resource.owner_id",
        "resource.status",
        "context.ip",
        "context.time"
      ]
    }
  }
}

Multiple Policies

When multiple active policies exist for the same permission, they are evaluated in descending priority order. All policies must pass (AND logic). If any one policy fails, access is denied even if others pass.

Use priority to control evaluation order when you want deterministic behaviour in logging or debugging.

Enabling ABAC in Permission Checks

To trigger policy evaluation, include resource or context in your authorization check request. Without these parameters, the check is pure RBAC with no policy overhead.

bash
curl -X POST \
  https://api.yorauth.com/api/v1/applications/your-application-id/authz/check \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "user-123",
    "permission": "posts:delete",
    "resource": { "owner_id": "user-456" }
  }'

Response when RBAC passes but policy fails:

json
{
  "allowed": false,
  "permission": "posts:delete",
  "cached": false,
  "abac_evaluated": true,
  "policies_checked": 1
}

abac_evaluated: true with policies_checked: 0 means RBAC passed but the permission has no discrete row in the database (it was matched via a wildcard). In this case, no policies can exist for the permission and access is granted.