Secure a CAP Service with @requires and @restrict

Use CDS security annotations to require authentication and restrict access by scope, inspect req.user in handlers, and test locally with mock users.

Secure a CAP Service with @requires and @restrict

CAP reads the Authorization: Bearer <token> header and exposes a typed req.user object in every handler — no middleware code needed.


Require authentication on an entire service

// srv/cat-service.cds
using bookshop from '../db/schema';

@requires: 'authenticated-user'
service CatalogService {
  entity Books    as projection on bookshop.Books;
  entity Orders   as projection on bookshop.Orders;
}

Any request without a valid JWT token gets HTTP 401 automatically.


Restrict individual entities by scope

@requires: 'authenticated-user'
service CatalogService {

  // Anyone logged in can read
  @readonly
  entity Books as projection on bookshop.Books;

  // Only users with the 'admin' scope can create/update/delete
  @restrict: [
    { grant: 'READ',   to: 'viewer' },
    { grant: ['WRITE', 'DELETE'], to: 'admin' }
  ]
  entity Orders as projection on bookshop.Orders;
}

The scope names (viewer, admin) must match the role-template names in xs-security.json (CAP strips the $XSAPPNAME. prefix automatically).


Read the user in a handler

// srv/cat-service.js
module.exports = class CatalogService extends cds.ApplicationService {
  init() {
    this.before('CREATE', 'Orders', req => {
      const { id, name, roles } = req.user;
      console.log(`Order placed by: ${name} (${id})`);
      console.log(`Has admin scope: ${req.user.is('admin')}`);

      // Stamp the order with the creator's ID
      req.data.createdBy = id;
    });
    return super.init();
  }
};
Property Value
req.user.id Subject claim from JWT (unique user ID)
req.user.name Display name
req.user.attr Custom XSUAA attributes
req.user.is('scope') Returns true if the token contains that scope
req.user.roles Array of all scopes in the token

Test locally with mock users

cds watch activates mock authentication automatically in development.
You can configure mock users in your package.json:

{
  "cds": {
    "requires": {
      "auth": {
        "kind": "mocked",
        "users": {
          "alice": { "roles": ["admin"] },
          "bob":   { "roles": ["viewer"] },
          "carol": {}
        }
      }
    }
  }
}

Pass the user as a Basic Auth header (mock only — never in production):

# Test as alice (admin)
curl -u alice: http://localhost:4004/catalog/Orders

# Test as carol (no roles) — should get 403 on restricted entities
curl -u carol: http://localhost:4004/catalog/Orders

Or with the VS Code REST Client:

### Read books (any logged-in user)
GET http://localhost:4004/catalog/Books
Authorization: Basic alice:

### Create order (admin only)
POST http://localhost:4004/catalog/Orders
Authorization: Basic alice:
Content-Type: application/json

{ "bookId": 1, "quantity": 2 }

Test with a real XSUAA token (hybrid profile)

Once you have a service key from your BTP XSUAA instance, you can test locally against real auth:

# Bind the service key for local use
cds bind xsuaa --to bookshop-xsuaa --kind xsuaa

# Run with the real XSUAA
cds watch --profile hybrid

With --profile hybrid, CAP validates real JWTs from XSUAA instead of mock tokens.
Get a token with the OAuth2 password grant (dev only):

curl -X POST https://your-tenant.authentication.eu10.hana.ondemand.com/oauth/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=password&username=your@email.com&password=yourpw' \
  -u '<clientid>:<clientsecret>'

The single most common mistake

Adding @requires to the service but forgetting to add xsuaa to the requires in package.json:

{
  "cds": {
    "requires": {
      "auth": { "kind": "xsuaa" }
    }
  }
}

Without this, CAP uses the mocked auth even in production and every user appears as anonymous.

Sign in for progress tracking & Q&A →