Skip to main content

Authentication is a solved problem. Between passkeys, OpenID Connect and a mature market of managed identity providers, proving who someone is has become a component you buy rather than a system you build. Most teams get it right on the first attempt.

Authorisation is not solved. Deciding what that person is allowed to do remains a scattering of conditional statements across controllers, middleware, database queries and frontend components, with no single source of truth and no way to audit it. The consequences show up in the data: Broken Access Control holds the A01 slot in the OWASP Top 10:2025, and OWASP reports that 100% of applications tested exhibited some form of it. In the API-specific list, Broken Object Level Authorization has occupied the API1 position continuously since 2019.

Those numbers are not a story about careless developers. They are a story about architecture. And 2026 is the year the tooling finally caught up.

TL;DR

  • Broken Access Control ranks A01 in the OWASP Top 10:2025, with 100% of tested applications showing some form of it. BOLA has been API1 in the API Security Top 10 since 2019.
  • The root cause is architectural: authorisation logic is duplicated across every layer of an application instead of living in one auditable place.
  • Externalised authorisation splits the decision (Policy Decision Point) from the enforcement (Policy Enforcement Point), an idea popularised by Google’s Zanzibar paper.
  • The OpenID Foundation ratified the AuthZEN Authorization API 1.0 in early 2026, giving the PDP/PEP boundary a standard JSON interface so policy engines become swappable.
  • OpenFGA reached CNCF Incubating status, and Zanzibar-style engines now run at genuine scale (OpenAI uses SpiceDB for ChatGPT Enterprise connectors).
  • AI agents make this urgent: role-based models cannot express “this agent, acting for this user, may read these three documents for the next twenty minutes”.

Why authorisation logic rots

Authorisation starts simple. A single if (user.role === 'admin') check in a controller. Then a second one in a different controller, subtly different. Then a WHERE owner_id = ? clause that encodes the same rule at the data layer. Then a conditional in the frontend that hides a button, which someone eventually mistakes for a security control. Then a background job that bypasses all four because it runs as a service account.

Within eighteen months, the answer to “who can edit this invoice?” is distributed across forty files in four languages, and nobody can answer it without reading code. There is no test that proves the rules are consistent, because there is nothing to test against. Every new feature is a fresh opportunity to forget a check, and forgetting a check is invisible until someone changes an ID in a URL and sees another customer’s data.

This is why broken access control is so persistent. Injection flaws have a fix you can apply mechanically: use parameterised queries. Broken access control has no equivalent, because the vulnerability is not a bad line of code. It is the absence of a line of code, in a system that has no idea which lines are supposed to exist.

The externalisation shift

The alternative pattern is old in enterprise identity circles and new to most product teams: separate the decision from the enforcement.

A Policy Enforcement Point (PEP) sits wherever access happens: an API handler, a gateway, a service boundary. It does not know the rules. It asks a question and obeys the answer. A Policy Decision Point (PDP) owns the model, evaluates the question and returns a verdict. Your authorisation logic lives in exactly one place, is versioned, is testable, and produces an audit trail as a side effect rather than as an afterthought.

Google described a production implementation of this in its Zanzibar paper, the system behind sharing permissions in Drive, YouTube and Cloud. The insight was to model permissions as a graph of relationships (this user is an editor of this folder, this folder is the parent of this document) rather than as flat role assignments. That is relationship-based access control, or ReBAC, and it turns out to describe how real products actually work far better than role lists do.

What changed in 2026

Two developments moved this from “interesting for platform teams at hyperscalers” to “a realistic decision for a fifteen-person engineering team”.

First, the interface got standardised. The OpenID Foundation ratified the AuthZEN Authorization API 1.0 following a voting period that closed in January 2026, publishing it on the Standards Track shortly after. It is a deliberately unglamorous JSON API for asking “can this subject perform this action on this resource?” and getting a decision back. The practical effect is that the PDP stops being a lock-in point. Interoperability demonstrations have already shown a single enforcement implementation switching between multiple independent policy engines by changing an endpoint URL.

Second, the engines matured. OpenFGA, which originated at Auth0 and Okta, advanced to CNCF Incubating status, which matters mostly as a governance signal: a project you can adopt without betting on one vendor’s roadmap. SpiceDB from AuthZed offers the closest fidelity to Zanzibar’s consistency guarantees and runs at serious scale, including OpenAI’s ChatGPT Enterprise connectors. On the policy-as-code side, Cerbos provides a stateless decision service that is genuinely simple to deploy, Open Policy Agent remains the choice when you want one engine spanning infrastructure and application concerns, and AWS Cedar covers teams already committed to that ecosystem.

The AI agent forcing function

If none of the above is urgent for your product, the agent layer probably is.

Role-based access control assumes a stable subject with a durable role. An AI agent is neither. It acts on behalf of a user, often across several systems, frequently in a session that should be narrowly scoped in both permission and time. The question your authorisation layer now needs to answer is not “is this user an editor?” but “is this agent, operating under a delegation from this user, permitted to perform this specific action on this specific record, right now?”

You cannot express that as a role. You can express it as a relationship graph with a delegation edge and an expiry, or as a policy with contextual attributes. Teams that hardcode agent permissions as a new set of service-account roles are building the exact sprawl they will need to unwind in a year. This is the same governance gap that shows up in agent identity management and API gateway design, and it has the same root: infrastructure built for humans being asked to reason about machines.

A decision framework

Stay with RBAC in your application if your permission model is genuinely flat, your role count is stable in the single digits, and resources are not shared between users in arbitrary ways. Plenty of B2B tools fit this. Do not build a policy service because it is fashionable.

Move to ReBAC when users share individual resources with each other, when permissions inherit through hierarchies (organisation, then workspace, then folder, then document), or when you need to answer “which documents can this user see?” efficiently across millions of rows. Nested sharing is the clearest signal.

Move to policy-as-code when decisions depend on attributes rather than relationships: time of day, geography, data classification, request context, regulatory conditions. Compliance-heavy domains land here.

Most non-trivial products end up needing both, which is one of the better arguments for adopting a standard interface early.

Migrating without a rewrite

You do not have to boil the ocean. A workable sequence:

  1. Inventory the decision points. Grep for every permission check in the codebase. The count itself is usually the argument that wins the internal debate.
  2. Centralise in-process first. Route every check through a single function, still in your application, still using your existing logic. This is unglamorous and it delivers most of the consistency benefit immediately.
  3. Write the model down. Define the resource types, relations and permissions explicitly. Doing this reliably uncovers rules nobody knew were in production.
  4. Extract to a PDP. Swap the internal function for a call to a policy engine, ideally over AuthZEN. Co-locate it as a sidecar so decision latency stays under a millisecond.
  5. Instrument. Log every decision with subject, action, resource and result. This is your access audit trail, and auditors will ask for it.

Two things to plan for honestly. Point checks are easy; list filtering is hard, and “return every document this user can read” is the query that will define your performance profile, so design for it from the start. And centralising authorisation state creates a consistency problem between your application database and your permission store, which is precisely why Zanzibar invented consistency tokens. Neither is a reason to avoid the pattern, but both are reasons to prototype before committing.

The takeaway

Broken access control has been the top web application risk for years, not because the industry lacks discipline but because the prevailing architecture makes correctness impossible to verify. Externalised authorisation replaces “hope every developer remembered” with “one model, one decision point, one audit log”. The standards and the engines are now good enough that this is a normal engineering decision rather than a research project.

At REPTILEHAUS we design and implement authorisation architecture for SaaS platforms, agentic AI systems and multi-tenant products, including ReBAC modelling, policy engine selection and migration from scattered in-application checks. If your permission model has outgrown its role column, or you are trying to work out how AI agents fit into a system built for humans, get in touch.

📷 Photo by Abi abdullah miyad on Unsplash