Full xs-security.json vocabulary: scopes, attributes, role-templates, oauth2-configuration, and $XSAPPNAME.
xs-security.json vocabulary: scopes, attributes, role templates, role collections$XSAPPNAME placeholder — why it's essentialxs-security.json is the security contract for your app. Every scope, role, and attribute you'll use in @requires and @restrict CDS annotations must be declared here first. Knowing the full vocabulary means you can express any authorization requirement without working around the framework.
{
"xsappname": "bookshop",
"tenant-mode": "dedicated",
"scopes": [
{
"name": "$XSAPPNAME.Admin",
"description": "Full administrative access"
},
{
"name": "$XSAPPNAME.Buyer",
"description": "Purchase books"
},
{
"name": "$XSAPPNAME.ViewReports",
"description": "View sales reports",
"granted-apps": ["$XSAPPNAME"] // restricts who can grant this scope
}
],
"attributes": [
{
"name": "region",
"description": "Sales region assigned to the user",
"valueType": "string"
},
{
"name": "costCenter",
"description": "User's cost center",
"valueType": "string"
}
],
"role-templates": [
{
"name": "Admin",
"description": "Administrator",
"scope-references": ["$XSAPPNAME.Admin"],
"attribute-references": []
},
{
"name": "RegionalBuyer",
"description": "Buyer with region restriction",
"scope-references": ["$XSAPPNAME.Buyer"],
"attribute-references": [
{ "name": "region" } // ← attribute available in this role
]
}
],
"oauth2-configuration": {
"token-validity": 43200,
"refresh-token-validity": 2592000,
"redirect-uris": [
"https://*.cfapps.eu10.hana.ondemand.com/**",
"http://localhost:5000/**"
],
"system-attributes": ["groups", "rolecollections"],
"user-attributes": ["region", "costCenter"]
}
}
xs-security.json defines:
scope → atomic permission (e.g., bookshop.Admin)
role-template → named group of scopes (e.g., Admin = [bookshop.Admin])
BTP Cockpit defines:
role → instance of a role template (admin can add attributes)
role-collection → named group of roles (e.g., "Bookshop Admins" = [Admin role])
User assignment:
BTP user → assigned to role-collection → gets the scopes → CAP reads from JWT
After assigning a role with attributes to a user:
// Decoded JWT — attributes from xs-security.json
{
"user_name": "alice",
"scope": ["bookshop.Buyer"],
"xs.user.attributes": {
"region": ["EMEA", "NA"], // user has both regions
"costCenter": ["CC-1001"]
}
}
// In CAP handler
req.user.attr.region // ['EMEA', 'NA']
req.user.attr.costCenter // ['CC-1001']
// WRONG — hardcoded app name
{ "name": "bookshop.Admin" }
// CORRECT — $XSAPPNAME is substituted at runtime
{ "name": "$XSAPPNAME.Admin" }
// Becomes: "bookshop.Admin" in dedicated tenant
// Becomes: "bookshop!t<tenant-id>.Admin" in shared tenant mode
Always use $XSAPPNAME. Hardcoded names break multitenant deployments.
// Dedicated — one subaccount, one XSUAA instance (most apps start here)
{ "tenant-mode": "dedicated" }
// Shared — same XSUAA serves multiple subscriber tenants (SaaS)
{ "tenant-mode": "shared" }
"oauth2-configuration": {
"token-validity": 3600, // 1 hour (access token)
"refresh-token-validity": 1209600, // 14 days (refresh token)
"autoapprove": true, // skip consent screen for trusted apps
"allowedproviders": ["sap.default"] // restrict to SAP IdP
}
region attribute to your XSUAA configurationRegionalBuyer role template that includes the region attributeregion=EMEA to your userxs.user.attributes.region is present$XSAPPNAME prefix for scope names — never hardcode the app namexs-security.json must be declared before use in @restrict wheretenant-mode: dedicated for single-tenant; shared for multitenant SaaS