Two annotations, full access control
CAP's authorization model is declarative: @requires guards whole services, @restrict guards entities with row-level rules. Together they cover nearly every real-world requirement without custom code.
@requires= who may enter the service.@restrict= who may do what, to which rows.
@requires: service-level gates
@requires : 'authenticated-user'
service CatalogService {
entity Books as projection on my.Books;
};
@requires : 'admin'
service AdminService {
entity Orders as projection on my.Orders;
};
The first service needs any logged-in user; the second needs the admin scope from your XSUAA setup. Unauthorized requests get a clean 403.
@restrict: fine-grained entity rules
service OrderService {
@(restrict: [
{ grant: 'READ', to: 'viewer' },
{ grant: ['READ','WRITE'], to: 'sales' },
{ grant: '*', to: 'admin' }
])
entity Orders as projection on my.Orders;
}
Viewers read, salespeople read and write, admins do everything. Grants map to OData operations — CAP translates them into CUD restrictions automatically.
Row-level security with where conditions
This is where @restrict earns its reputation:
@(restrict: [
{ grant: 'READ',
to: 'sales',
where: 'region = $user.region' }
])
entity Orders as projection on my.Orders;
Each salesperson sees only their region's orders. $user exposes JWT attributes — map IdP attributes (department, region, cost center) into the token via XSUAA configuration, then filter on them.
Row-level rules apply at the database query level, not in application code. They can't be bypassed by clever OData queries.
Instance-based vs static
- Static (
to: 'role') — same rule for all rows. Fast, simple, covers most cases. - Dynamic (
where: 'owner = $user.id') — per-row evaluation. "Users see their own records." - Combine — multiple restrict entries are OR'd; a user matching any entry gets that grant.
- Test as different users — authorization bugs are silent; verify with viewer, sales, and admin identities.
Testing authorization properly
Authorization bugs are silent — the app works for you (admin) and fails for everyone else. Test with at least three identities: an admin, a restricted role, and an unauthenticated request.
For automated tests, CAP supports mock users with specific roles. Assert the matrix explicitly: viewer gets 200 on READ and 403 on CREATE; sales gets 200 on both; anonymous gets 401 everywhere.
Row-level rules need data-level tests: create records for two regions, log in as each region's user, and verify neither sees the other's rows. Query-level enforcement means this either works or it doesn't — there's no middle ground to debug.
Declare it, don't code it
Resist the urge to hand-roll authorization in handlers. Annotations are visible, auditable, and enforced consistently — including for actions and functions.
Hit a tricky authorization scenario? Describe it in the comments.