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
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
| Field | Type | Description |
|---|---|---|
id | UUID | Unique policy identifier |
application_id | UUID | Application this policy belongs to |
permission_id | UUID | The permission this policy applies to |
name | string | Human-readable policy name |
description | string | Optional description |
expression | string | Policy condition (JSON object or expression string) |
expression_type | string | json or expression |
on_missing_attr | string | deny (default) or permit when an attribute is absent |
is_active | boolean | Inactive policies are not evaluated |
priority | integer | Higher priority policies are evaluated first |
created_at | ISO 8601 | Creation timestamp |
Expression Types
JSON (recommended)
A structured JSON object using predefined operators. Safer to store and validate, and offers rich operator support.
Supported operators:
| Operator | Description |
|---|---|
and | All child conditions must pass |
or | At least one child condition must pass |
not | Inverts a condition |
eq | Equal |
neq | Not equal |
gt / gte | Greater than / greater than or equal |
lt / lte | Less than / less than or equal |
in | Value is in a list |
not_in | Value is not in a list |
contains | String or array contains a value |
starts_with / ends_with | String prefix/suffix check |
matches | Regex match (max 200-char pattern, limited backtracking) |
not_null / is_null | Attribute presence check |
time_between | Current time is within a range (uses context.time) |
day_of_week | Current day matches a list (uses context.day_of_week) |
ip_in_cidr | IP 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.
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:
| Namespace | Source |
|---|---|
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 inHH:MMformatcontext.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:
{
"eq": ["user.id", "resource.owner_id"]
}
Business hours restriction
Allow reports:export only during business hours on weekdays:
{
"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:
{
"ip_in_cidr": ["context.ip", "203.0.113.0/24"]
}
Department-based access
Only allow salaries:view for users in the finance department:
{
"eq": ["user.department", "finance"]
}
Combined conditions
Allow documents:publish only for senior users in specific roles publishing non-draft content:
{
"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
GET /api/dashboard/applications/{applicationId}/policies
Query parameters:
| Parameter | Description |
|---|---|
permission_id | Filter by a specific permission UUID |
per_page | Results per page (max 100) |
Response 200 OK:
{
"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
POST /api/dashboard/applications/{applicationId}/policies
Request body:
{
"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 syntaxjson{ "error": { "code": "INVALID_POLICY_EXPRESSION", "message": "Unknown operator: bad_op" } }402 Payment Required— ABAC policy limit reached for your plan
Update a Policy
PUT /api/dashboard/applications/{applicationId}/policies/{policyId}
All fields are optional. Set is_active: false to disable a policy without deleting it.
Request body:
{
"is_active": false
}
Response 200 OK: Returns the updated policy object.
Delete a Policy
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.
POST /api/dashboard/applications/{applicationId}/policies/{policyId}/simulate
Request body:
{
"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:
{
"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.
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:
{
"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.