| name | security-auth |
| description | Use when implementing security and authorization in SAP CAP: authentication, @requires, @restrict, role-based access, XSUAA, IAS, grant, to, where clause, req.user, user roles, instance-based authorization, row-level security, xs-security.json, scopes, role templates, user attributes.
|
| metadata | {"category":"cap","version":"1.0.0","keywords":["@requires","@restrict","grant","to","where","XSUAA","IAS","role","auth","permission","access control","req.user","xs-security.json","scope","row-level security"],"related":{"cds-modeling":"entities and services that need authorization annotations","service-handlers":"check req.user inside business logic","btp-deployment":"configure XSUAA service binding in mta.yaml","multitenancy":"tenant-aware authorization patterns"}} |
Security & Authorization — CAP Best Practices
Primary reference: https://cap.cloud.sap/docs/guides/security/overview
Authorization: https://cap.cloud.sap/docs/guides/authorization
Service-level access control
// Restrict entire service to authenticated users
@requires: 'authenticated-user'
service CatalogService { ... }
// Multiple roles (OR logic)
@requires: ['admin', 'manager']
service AdminService { ... }
Entity-level @restrict
service OrderService @(requires: 'authenticated-user') {
entity Orders @(restrict: [
{ grant: 'READ', to: ['viewer', 'editor', 'admin'] },
{ grant: 'CREATE', to: ['editor', 'admin'] },
{ grant: 'UPDATE', to: ['editor', 'admin'], where: 'createdBy = $user' },
{ grant: 'DELETE', to: 'admin' },
]) as projection on db.Orders;
}
Instance-based authorization (row-level security)
❌ Wrong vs ✅ Correct syntax
// ❌ WRONG — operations instead of grant, roles instead of to, where as array
entity Orders @(restrict: [
{ operations: ['READ'], roles: ['authenticated-user'] },
{ operations: ['UPDATE'], where: ['owner = $user.id'] }
])
// ✅ CORRECT
entity Orders @(restrict: [
{ grant: 'READ', to: 'authenticated-user' },
{ grant: 'UPDATE', to: 'editor', where: 'createdBy = $user' }
])
// User can only update their own records
{ grant: 'UPDATE', to: 'editor', where: 'createdBy = $user' }
// Filter by user attribute (e.g. cost center)
{ grant: 'READ', to: 'employee', where: 'costCenter = $user.attr.costCenter' }
Expose user attributes from XSUAA/IAS in xs-security.json:
{
"role-templates": [{
"name": "Employee",
"attribute-references": ["costCenter"]
}]
}
Checking authorization in handlers
async init() {
this.before('UPDATE', Orders, this.checkOwner)
}
async checkOwner(req) {
const { user } = req
if (user.is('admin')) return
const order = await SELECT.one(Orders, req.params[0]).columns('createdBy')
if (!order) req.reject(404)
if (order.createdBy !== user.id) req.reject(403, 'You can only edit your own orders')
}
Programmatic role checks
const { user } = req
user.is('admin')
user.id
user.tenant
user.attr.costCenter
user.locale
xs-security.json — XSUAA scopes and roles
{
"xsappname": "my-cap-app",
"tenant-mode": "dedicated",
"scopes": [
{ "name": "$XSAPPNAME.admin", "description": "Administrative access" },
{ "name": "$XSAPPNAME.editor", "description": "Create and edit records" },
{ "name": "$XSAPPNAME.viewer", "description": "Read-only access" }
],
"role-templates": [
{
"name": "Admin",
"scope-references": ["$XSAPPNAME.admin", "$XSAPPNAME.editor"
Local development mock users
In .cdsrc.json or package.json:
{
"cds": {
"requires": {
"auth": {
"kind": "mocked",
"users": {
"alice": { "roles": ["admin"] },
"bob": { "roles": ["editor"] },
"carol": { "roles": ["viewer"], "attr": { "costCenter": "CC100" } }
}
}
}
}
}
Test with: http://localhost:4004 --user alice
IAS (Identity Authentication Service) setup
{
"cds": {
"requires": {
"auth": "ias"
}
}
}
Bind the identity service in mta.yaml:
- name: my-app-srv
requires:
- name: my-ias-instance
Common mistakes to avoid
- ❌ Using
@requires: 'any' when you mean 'authenticated-user' (they differ!)
- ❌ Relying only on client-side checks — always enforce in the service layer
- ❌ Hardcoding user IDs in
where clauses instead of using $user
- ❌ Forgetting to add scopes to
xs-security.json — roles without scopes don't work
- ❌ Not testing with different mock users locally before deploying
- ❌ Using
@restrict without @requires on the service — annotations don't cascade automatically