Last month, I watched a developer debug a production authorization issue for three hours. The culprit? A typo in an attribute name. The policy expected departmentId
, but the application was sending department_id
. Classic.
This scenario plays out more often than you'd think. When you write Cerbos policies, you're essentially making assumptions about the shape of data your application will send. Without schemas, those assumptions are just that: assumptions. Your policies become a house of cards waiting for the wrong payload to knock everything down.
Cerbos policies operate on attributes. Principal attributes describe who's making the request. Resource attributes describe what they're trying to access. By default, Cerbos accepts any JSON structure you throw at it. This flexibility feels great when you're prototyping, but it becomes a liability at scale.
I learned this the hard way at a previous gig. We had dozens of microservices, each sending slightly different attribute formats to our authorization layer. One service sent boolean values as strings. Another used snake_case while everyone else used camelCase. The authorization policies worked, technically, but understanding what data each policy expected required archaeology through multiple codebases.
What we needed was a contract. Something that would tell us immediately when a service sent the wrong shape of data. That's exactly what schemas give you in Cerbos.
Cerbos uses JSON Schema, specifically draft 2020-12. You define schemas for your principal and resource attributes, then reference them directly in your resource policies. The implementation is straightforward. You store your schemas in a _schemas
directory at the root of your policy storage, then reference them in your policies.
Here's what caught my attention when I first started using them. You can compose schemas using standard JSON Schema features like $ref
. If you have common attributes across multiple resources, define them once and reference them everywhere. No duplication, no drift.
The real power comes from the enforcement levels. Set Cerbos to warn
mode during development to log validation errors without breaking anything. Once you're confident, switch to reject
mode and watch invalid requests get blocked before they can cause damage.
Catch integration errors during testing, not in production. Remember that attribute name mismatch I mentioned? With schemas, your integration tests would fail immediately with a clear validation error pointing to the exact field that's wrong. No more debugging sessions that end with "oh, it's a typo."
Documentation that can't lie. Every developer on your team can look at a schema and know exactly what attributes a policy expects. The schema is the documentation, and Cerbos enforces it. I've seen teams reduce onboarding time for new developers by half just because they could understand the authorization requirements without diving into policy logic.
Prevent attribute injection attacks. Without schemas, a malicious actor could potentially send unexpected attributes that might interact with your policies in unintended ways. With schemas, you define exactly what attributes are allowed. Everything else gets rejected. It's like input validation for your authorization layer.
During a workshop last year, several developers hit the same issue. They defined strict schemas for their resources, then wondered why CREATE actions kept failing. The resource doesn't exist yet during creation, so it might not have all the required attributes.
Cerbos thought of this. You can use the ignoreWhen
directive to skip schema validation for specific actions. Set it up for CREATE and DELETE operations where your resource might be in a transitional state. This flexibility lets you be strict where it matters while staying practical where you need to be.
Start small. Pick one critical resource type and define schemas for it. Run in warn
mode for a week and watch your logs. You'll quickly see patterns in validation errors that point to inconsistencies in your system.
I typically define a base principal schema that all resources share, then extend it for specific use cases. Same with resources. Build a library of common attribute definitions and compose them as needed. Think of it like building with LEGO blocks rather than sculpting from clay.
The migration path is smooth. Existing policies without schemas continue to work. You add schemas resource by resource, gradually increasing coverage. No big-bang deployments, no service interruptions.
A customer once told me about their "Friday afternoon incident." A developer pushed a small change that accidentally renamed a critical attribute. The policies still loaded fine. The application started fine. But every authorization check that relied on that attribute started failing in subtle ways.
Without schemas, these errors manifest as incorrect authorization decisions. Users might get access they shouldn't have, or lose access they should have. You won't get a clear error message. You'll get confused users and a lot of head-scratching.
With schemas, that same change would have been caught immediately. Either during testing if you have good coverage, or at runtime with a clear validation error. The difference between a five-minute fix and a weekend war room.
Schemas do more than prevent bugs. They create an auditable specification of your authorization requirements. During security reviews, auditors can examine your schemas to understand exactly what data drives authorization decisions. No guesswork, no assumptions.
I've watched security teams breathe a sigh of relief when they realize they can review schemas instead of parsing through policy logic to understand data dependencies. It transforms authorization from a black box into a well-defined system with clear inputs and outputs.
Your schemas become part of your security posture. They document not just what you accept, but what you explicitly reject. That's powerful when you need to demonstrate compliance or investigate an incident.
Write your first schema. Make it simple. Cover the basic attributes you know every principal will have: id, roles, maybe department. Store it in _schemas/principal.json
. Reference it in one resource policy. Set enforcement to warn
. Deploy it.
Watch what happens for a day. Fix any validation errors that pop up. Once clean, switch to reject
mode. Congratulations, you've just made your authorization system significantly more robust.
The overhead is minimal. The safety you gain is substantial. Every team I've worked with that adopted schemas wondered why they didn't do it sooner. Stop trusting your authorization to unvalidated data. Define your contracts, enforce them, and sleep better knowing your policies work with the data they actually receive.
If you haven’t tried Cerbos Hub yet, you can learn more and try it for free here. You can also book a call with a Cerbos engineer to see how our solution can help streamline access control in your applications.
Book a free Policy Workshop to discuss your requirements and get your first policy written by the Cerbos team
Join thousands of developers | Features and updates | 1x per month | No spam, just goodies.