Here's a question I'd ask any platform team serving regulated customers: where does your authorization logic actually live? Not the answer on the architecture diagram — the real one. If you're honest, it lives everywhere. There's a check in the API gateway, another buried in a service handler, a third stitched into a database query with a tenant ID in the WHERE clause, and a fourth that someone added under deadline pressure and never wrote down. Authentication you probably nailed years ago. Authorization is the mess nobody wants to open.
I run security and DevOps for a fintech that serves more than 1,500 financial institutions, and I'll tell you plainly: the moment you're multi-tenant and regulated, scattered authorization stops being a code-quality problem and becomes a business risk. Every one of those institutions has its own expectations about who can see what, which of their end-customers' data is in play, and what your auditors — and theirs — can demonstrate on demand. You cannot answer "who can access this account, and why?" if the answer is distributed across forty code paths in six services.
The real mechanic: authorization is data, not control flow
The mistake almost everyone makes is treating authorization as application logic. It feels like logic — if user.role == admin and account.fi_id == user.fi_id — so it ends up as if statements next to the business code. But the thing you actually care about isn't the branch. It's the policy: the durable statement of who may do what, to which resource, under which conditions. Policy is data. And once you accept that authorization is data, the architecture writes itself. You externalize it.
This is what AWS Verified Permissions and its policy language, Cedar, are built for. You move your authorization rules out of the application and into a centralized policy store. Your application stops deciding and starts asking: it sends the principal, the action, the resource, and the context to an authorization engine and gets back a permit or deny. The decision logic lives in Cedar policies that are versioned, reviewable, and testable on their own. Cedar is a purpose-built, analyzable language — you can reason about what a policy allows without running it — and it's open source, so you're not locked into a single decision point if you want to embed the engine elsewhere.
For a multi-tenant fintech, the payoff is in the shape of the policies. A single policy can express "a user may read an account only when the account's owning institution matches the user's institution" — once, centrally, instead of as a fragile tenant-ID check copy-pasted into every query. Per-FI overrides become additional scoped policies rather than special-case branches. Per-customer consent — the kind of thing that actually matters when you're handling consumer financial data — becomes context you pass into the decision, not a flag you hope someone remembered to check.
Why this specifically matters when you're regulated
Two reasons, and they're the reasons I push this pattern internally.
The first is provability. When an examiner or a partner's due-diligence team asks how you enforce data segregation between institutions, "we have checks in the code" is a losing answer. "Here is the policy store, here are the versioned policies that enforce tenant isolation, here is the change history, and here is the test suite that proves a user from FI A can never read FI B's data" is a winning one. Externalized authorization turns an unauditable sprawl into a single, inspectable artifact. I've written before that a mature security program is a sales asset — this is a concrete instance of it. Provable isolation closes deals in this industry.
The second is blast radius. When authorization lives in code, every authz change ships through the full deploy pipeline, and every authz bug is a code bug that could already be in production across every service. When it's centralized, you change a policy, you review it like the high-stakes artifact it is, and you can roll it back without a redeploy. You also get one place to log every decision — which means one coherent audit trail of who was permitted to do what, instead of reconstructing intent from scattered application logs after the fact.
Where the work actually is
I won't pretend this is free, because the failure mode is real. The hard part isn't standing up the service — it's modeling your domain honestly. You have to define your principals, resources, and actions cleanly, and in a multi-tenant world that means getting rigorous about your entity hierarchy: institutions, the users within them, the end-customers and accounts they're entitled to, and the relationships between all of it. Cedar leans on that entity model to make group- and hierarchy-based rules sane, so the data model is the project. Get it wrong and you've just moved your mess into a new system.
The other discipline is resisting the urge to externalize everything on day one. Start with the authorization that's both highest-risk and most cross-cutting — tenant isolation and per-FI access scoping — and prove the pattern there. Leave trivial UI-gating where it is. The goal isn't purity; it's getting the decisions that auditors and customers care about out of the application and into a place you can govern.
So here's my challenge. Go find every place in your platform that decides whether one institution can see another's data. Count them. If that number is greater than one, you don't have an authorization model — you have an authorization incident waiting for a trigger. Pull those decisions into one policy store, make them data, and make them provable. In regulated fintech, that's not refactoring. That's the difference between being able to answer the hard question and hoping nobody asks it.
