From anonymous to authenticated
Local development runs without security — convenient, until you deploy. Production CAP apps on BTP authenticate users through XSUAA (Authorization and Trust Management). Here's the full setup, end to end.
Authentication answers "who are you?" Authorization answers "what may you do?" This post covers the first; the next covers the second.
Step 1: The security descriptor
Create xs-security.json in your project root:
{
"xsappname": "bookshop",
"scopes": [
{ "name": "$XSAPPNAME.admin", "description": "Admin access" },
{ "name": "$XSAPPNAME.viewer", "description": "Read access" }
],
"role-templates": [
{ "name": "admin", "scope-references": ["$XSAPPNAME.admin"] },
{ "name": "viewer", "scope-references": ["$XSAPPNAME.viewer"] }
]
}
Scopes are the permissions your app defines; role templates bundle them into assignable roles.
Step 2: Wire it into the MTA
In mta.yaml, add the XSUAA resource and bind your backend to it:
resources:
- name: bookshop-auth
type: org.cloudfoundry.managed-service
parameters:
service: xsuaa
service-plan: application
path: ./xs-security.json
On deploy, BTP creates the XSUAA service instance from this descriptor and injects the credentials into your app.
Step 3: Add the approuter
Users never call your backend directly — the approuter handles login and forwards the JWT:
{
"welcomeFile": "index.html",
"authenticationMethod": "route",
"routes": [
{ "source": "^/odata/(.*)$", "target": "$1",
"destination": "srv-api", "authenticationType": "xsuaa" },
{ "source": "^(.*)$", "target": "$1",
"localDir": "resources" }
]
}
Unauthenticated users hitting /odata/… get redirected to the identity provider login. After login, the JWT flows to your service on every request.
Step 4: Assign roles and test
- Create role collections in the BTP cockpit from your role templates.
- Assign users to the collections.
- Test — hit the app URL in an incognito window; you should land on the login page.
- Check
req.userin a handler to see the authenticated identity and scopes.
Local testing with auth:
cds bindor mock users via[development] usersconfig — don't deploy just to test login.
How the JWT flows
Understanding the token flow demystifies most XSUAA issues. The user logs in through the identity provider; the approuter receives the ID token, exchanges it for a JWT scoped to your xsappname, and forwards it to your backend.
Your CAP service validates the JWT signature against XSUAA's public keys and populates req.user: the user ID, scopes, and any custom attributes. Handlers and @restrict rules read from this — never from client-supplied headers.
When authentication breaks, check the chain in order: approuter routes, XSUAA binding, then the JWT contents (decode it at jwt.io — expiry and audience issues are visible immediately).
Security is infrastructure, not code
Notice how little application code this took: a JSON descriptor, MTA wiring, approuter routes. CAP keeps authentication declarative — your handlers just read req.user.
XSUAA errors cryptic? Paste them in the comments.