Understanding why modern authorization sometimes needs more than roles, scopes, and permissions.
Working around authentication and identity products has taught me that there’s almost always another layer to understand.
Authentication was relatively straightforward for me.
Who are you?
Then came authorization.
Now that we know who you are, what are you actually allowed to do?
OAuth scopes helped me understand that part.
A token might have permission to read:billing, for example. If it doesn't have that scope, it shouldn't be able to perform that action.
Simple enough.
But then I came across a question that made me realize scopes don't tell the whole story:
What if someone should have access to billing, but only under certain conditions?
Maybe they can access it only during business hours.
Maybe the request needs to come from an approved network.
Maybe the person also needs to belong to a particular group.
Now the authorization question isn't simply:
Can this token read billing data?
It becomes:
Can this particular user perform this particular action on this resource under these particular conditions?
That's the problem that helped me understand why policy-based authorization exists.
And eventually, why Cedar started making sense to me.
First, What Is Cedar?
Cedar is an open-source policy language created by AWS for fine-grained authorization.
The easiest way I found to think about it is this:
Instead of burying every access rule inside application logic, you describe the conditions under which access should be allowed or denied as policies.
A policy engine then evaluates those rules when an access decision needs to be made.
One distinction is important here:
Cedar evaluates the authorization request. It doesn't enforce access itself.
It looks at the request and applicable policies and returns an allow or deny decision. The surrounding system then acts on that decision.
What I find useful about this model is that the access rule becomes much easier to reason about.
Instead of thinking through conditions scattered across different pieces of code, you're answering a structured authorization question.
The Four Parts of an Authorization Decision
One concept made Cedar much easier for me to understand.
An authorization request can be thought about using four pieces:
1. Principal: Who is making the request?
This could be a user, service, workload, or another identity requesting access.
2. Action: What are they trying to do?
For example:
read
update
delete
3. Resource: What are they trying to access?
This could be an invoice, API, document, account, or another protected resource.
4. Context: What else about the request matters?
This is where additional information can become part of the authorization decision, such as the time of the request or where the request is coming from.
Put those four together and the question becomes:
Who can do what, to which resource, and under what conditions?
That framing made policy-based authorization much less abstract for me.
So, Why Aren't OAuth Scopes Enough?
This was probably the most useful distinction for me.
Scopes and audiences already solve important problems.
An audience can indicate which API a token is intended for.
A scope can indicate what the token is allowed to do.
For example:
read:billing
That works when the rule is simply:
This client can read billing information.
But real access requirements can become more specific.
Maybe the actual requirement is:
Allow billing access only for members of the admin group.
Or:
Allow access only during a particular time window.
Or:
Allow access only when the request comes from an approved network.
The scope hasn't changed. It's still:
read:billing
What changed are the conditions under which that permission should be granted.
And that's where relying only on static scopes starts becoming limiting.
What Happens Without a Policy Layer?
Those additional authorization checks still have to live somewhere.
An API gateway might check the IP address.
Middleware might check the time.
Application code might check the user's group.
Individually, every check can make sense.
The problem appears when you try to understand the complete access rule.
Imagine someone asks:
Who can access this API, and under exactly what conditions?
If those conditions are spread across gateways, middleware, and application code, there may not be one obvious place to find the answer.
You have to trace it across the system.
From a product perspective, this is what I find interesting about authorization.
It's not only about whether a system can enforce a rule.
It's also about whether teams can understand the rule being enforced.
That's one of the ideas behind using policies.
Instead of distributing every authorization condition across different pieces of logic, policies provide a way to express those rules more explicitly.
How Cedar Thinks About Allow and Deny
At a basic level, Cedar policies use two important effects:
permit
A permit policy describes when an action should be allowed.
forbid
A forbid policy describes when an action should explicitly be denied.
Cedar evaluates those policies against the principal, action, resource, and context of the request.
There are three behaviors I found particularly useful to understand.
Access is denied by default
If no applicable policy permits the request, access is denied.
forbid takes priority
If an applicable policy permits something but another applicable policy forbids it, the request is denied.
Policies are order-independent
The result doesn't depend on which policy happens to appear first.
You don't need to know the entire Cedar syntax to understand why these behaviors matter.
They make the authorization model more predictable.
Where I Came Across Cedar
I started looking more closely at Cedar through my work around MonoCloud.
MonoCloud uses Cedar for API Access Policies, where policies can be evaluated before an access token is issued.
This was the part that made the difference between scopes and policies much clearer to me.
Think about a client requesting:
read:billing
The scope still describes the permission being requested.
But a policy can evaluate whether that permission should actually be granted under the current conditions.
For simpler rules, policies can be configured using structured fields. More advanced rules can use Cedar directly when additional context needs to be evaluated.
So these two things aren't necessarily competing approaches.
They answer different questions.
Scope:
What permission is being requested?
Policy:
Under these conditions, should that permission actually be granted?
That distinction is what finally made the concept click for me.
A Simple Way to Think About It
If I had to explain the progression simply, I'd put it like this:
Authentication
Who are you?
↓
Basic authorization
What are you allowed to do?
↓
Context-aware authorization
Are you allowed to do this specific thing, to this specific resource, under these specific conditions?
Scopes are still useful.
Roles are still useful.
Permissions are still useful.
Policies become valuable when the answer starts depending on more context.
What I Took Away From Learning About Cedar
Before working around identity products, I mostly thought about authorization in terms of roles and permissions.
Admin can do X.
User can do Y.
This token has this scope.
Those models still matter.
But the more I've learned about authorization, the more I've realized that some of the difficult questions begin after you've established the basic permission.
Can they do it right now?
Can they do it from here?
Can they do it to this resource?
Does another condition change the decision?
That's what made policy-based authorization click for me.
Cedar is one way of expressing those rules in a form that a system can evaluate while keeping the authorization logic explicit and readable.
And for me, learning it from the product side, that was far more useful than simply memorizing another security term.
Top comments (0)