Configure .cdsrc.json mock users with roles and attributes. Write positive and negative auth tests with cds.test().
.cdsrc.jsoncds.testEvery developer needs to test auth scenarios locally — without an XSUAA service instance, without network latency, without shared credentials. The mocked auth system gives you a production-faithful simulation that runs entirely offline.
// .cdsrc.json
{
"requires": {
"auth": {
"[development]": {
"kind": "mocked",
"users": {
"alice": {
"ID": "alice",
"password": "alice",
"roles": ["Buyer"],
"attr": {
"region": "EMEA",
"costCenter": "CC-1001"
},
"tenant": "tenant-A"
},
"bob": {
"ID": "bob",
"password": "bob",
"roles": ["Admin","Buyer"],
"attr": {
"region": "NA"
},
"tenant": "tenant-A"
},
"carol-analyst": {
"ID": "carol",
"password": "carol",
"roles": ["Analyst"],
"tenant": "tenant-B"
},
"*": {
"roles": [],
"tenant": "tenant-default"
}
}
},
"[production]": {
"kind": "xsuaa"
}
}
}
}
"*" defines the default user — anyone with an unknown username gets these settings.
### As Alice (Buyer, EMEA)
GET http://localhost:4004/orders/Orders
Authorization: Basic alice:alice
### As Bob (Admin + Buyer, NA)
POST http://localhost:4004/orders/Orders
Authorization: Basic bob:bob
Content-Type: application/json
{ "title": "Test Order" }
### Unauthenticated
GET http://localhost:4004/orders/Orders
# No Authorization header — gets 401 if service requires authentication
// test/orders.test.js
const cds = require('@sap/cds')
describe('Order Service', () => {
// Set up test suite
const alice = { user: 'alice', password: 'alice' }
const bob = { user: 'bob', password: 'bob' }
const { GET, POST, DELETE } = cds.test('.').in(__dirname + '/..')
it('Alice can read own orders', async () => {
const { status, data } = await GET('/orders/Orders', { auth: alice })
expect(status).toBe(200)
expect(data.value.every(o => o.buyer_ID === 'alice')).toBe(true)
})
it('Alice cannot delete orders', async () => {
const { status } = await DELETE('/orders/Orders(some-uuid)', { auth: alice })
expect(status).toBe(403)
})
it('Bob can delete orders', async () => {
const { status } = await DELETE('/orders/Orders(some-uuid)', { auth: bob })
expect(status).toBe(204)
})
it('Unauthenticated users get 401', async () => {
const { status } = await GET('/orders/Orders')
expect(status).toBe(401)
})
})
// .cdsrc.json — tenants in mock config
"carol-analyst": {
"tenant": "tenant-B" // different tenant from alice/bob
}
it('tenant B cannot see tenant A data', async () => {
const carol = { user: 'carol-analyst', password: 'carol' }
const { data } = await GET('/orders/Orders', { auth: carol })
// Orders created by tenant-A users should not appear for tenant-B
expect(data.value).toHaveLength(0)
})
# Dev: use mocked auth
cds watch
# Staging: use real XSUAA
CDS_ENV=staging cds watch
# → picks up [staging] profile → "kind": "xsuaa"
// Log every request's auth context
this.before('*', '*', (req) => {
console.log({
event: req.event,
target: req.target?.name,
user: req.user?.id,
roles: req.user?.roles,
tenant: req.user?.tenant,
attr: req.user?.attr
})
})
# Check auth headers in cds watch output
cds watch --verbose
# Enables request/response logging including auth
□ Unauthenticated request → 401
□ Authenticated, wrong role → 403
□ Authenticated, correct role, own data → 200
□ Authenticated, correct role, other user's data → filtered (not 403)
□ Admin role → sees all data
□ Service-to-service (no user) → check handling
□ Expired token simulation → not easy in dev, test on staging
.cdsrc.json support roles, attributes, and tenant simulationcds.test() with { auth: { user, password } } simulates authenticated requests"*" user in mock config is the fallback for undefined usersCDS_ENV=staging) to test against real XSUAA