---
title: "Decide access in one policy module · Agent Wranglers"
url: https://agentwranglers.se/playbook/application-security/access-control/policy-decision-point
id: APS-101
level: Recommended
updated: 2026-10-27
build: a2a6bdfed830
tokens: 1126
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Decide access in one policy module

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Access control](https://agentwranglers.se/playbook/application-security/access-control.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-27.

- ID: APS-101
- Level: Recommended
- Chapter: [Access control](https://agentwranglers.se/playbook/application-security/access-control.md)
- Rule: Answer every access question through one policy function or service, the policy decision point. Every route asks it "may this user do this action on this record?" and never decides alone.
- Check: A test lists every route the server answers. It fails on a non-public route whose handler never calls the policy function. The policy tests cover every role and action pair in the permission table. A test that removes the ownership check makes at least one of them fail.
- How: The route is the enforcement point. It loads the record, then calls one function such as `can(user, 'refund', order)` and refuses on a no. Keep all rules in that module, as code or as policy files for an engine such as Open Policy Agent, Cedar or Casbin. Test each role and action pair there, once, instead of in every route. Check ownership and tenant inside the policy, so a route cannot forget them. Log every refusal with the user, action and record ID. Refuse when the policy itself fails, as [Deny by default and when a check fails](https://agentwranglers.se/playbook/application-security/access-control/fail-closed.md) says. Check ownership as [check ownership on every record](https://agentwranglers.se/playbook/accounts/roles-and-permissions/object-access.md) says. Roles and permissions themselves are designed as [Accounts and identity](https://agentwranglers.se/playbook/accounts/roles-and-permissions.md) describes, and multi-tenant data as the [tenant isolation rules](https://agentwranglers.se/playbook/data/multi-tenancy.md) say.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### JavaScript example

```js
const POLICY = {
  'order:read': (user, order) => order.customerId === user.id || user.roles.includes('support'),
  'order:refund': (user, order) => user.roles.includes('support') && order.tenantId === user.tenantId,
};
export const can = (user, action, record) => Boolean(POLICY[action]?.(user, record));

app.post('/orders/:id/refund', async (req, res) => {
  const order = await orders.find(req.params.id);
  if (!order || !can(req.user, 'order:refund', order)) return res.sendStatus(404);
  res.json(await refunds.create(order));
});
```

### Python example

```python
POLICY = {
    "order:read": lambda user, order: order.customer_id == user.id or "support" in user.roles,
    "order:refund": lambda user, order: "support" in user.roles and order.tenant_id == user.tenant_id,
}

def can(user, action, record) -> bool:
    rule = POLICY.get(action)
    return bool(rule and rule(user, record))
```

## Sources

- [OWASP Authorization Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html)
- [NIST SP 800-162 Guide to Attribute Based Access Control](https://csrc.nist.gov/pubs/sp/800/162/upd2/final)

## Related rules

- APS-20 [Deny by default and when a check fails](https://agentwranglers.se/playbook/application-security/access-control/fail-closed.md) in Application security
- ACC-77 [Check ownership on every record](https://agentwranglers.se/playbook/accounts/roles-and-permissions/object-access.md) in Accounts and identity

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/application-security/access-control/policy-decision-point">Agent Wranglers application security playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Deny by default and when a check fails](https://agentwranglers.se/playbook/application-security/access-control/fail-closed.md)
- Next: [Accept only the fields you expect](https://agentwranglers.se/playbook/application-security/access-control/mass-assignment.md)
- [Access control](https://agentwranglers.se/playbook/application-security/access-control.md)
