Use mocking to embrace auth in your domain model from the outset
- What the mocked authentication strategy is and how to use it
Prerequisites
You will need a development environment for CAP Node.js. See the tutorial Set up a self-contained development environment for CAP Node.js The assumptions in this tutorial are based on option 1 or option 2 in that tutorial, in that you have a VS Code (or GitHub Codespace) environment based on the foundation repository used in the setup described there, which also means that your starting directory will be /workspaces/cap-nodejs-dev-env. If you have your own CAP Node.js development environment setup, then please make the appropriate adjustments where necessary.
The CAP framework offers various auth strategies, including ones that support JWT, XSUAA and IAS based solutions. It also offers the mock strategy where Basic Authentication is used in combination with a simple list of pre-defined users and roles, which you can add to to suit your domain and authentication & authorization modeling.
- Step 1
The OData Deep Dive mission is based around a cut-down version of the classic Northwind service called “Northbreeze”. That service is available in the repository https://github.com/SAP-samples/odata-dd-server and is a good basis for our exploration of the mocked authentication strategy in this tutorial.
👉 Clone the repository:
shellCopygit clone https://github.com/SAP-samples/odata-dd-server👉 Now open the
northbreeze/directory within the cloned repository in a new VS Code / Codespace window:shellCopycode odata-dd-server/northbreeze/This should present just the Northbreeze project in the Explorer, at the root. Use the CAP Project Explorer feature to get a feel for the project’s service and entity definitions (you may need to use the Refresh button, in the form of a circular arrow, to get the explorer to re-read the project configuration):

- Step 2
Out of the box, the CDS model in this Northbreeze project comes with a single restriction on the
Categoriesprojection, in the form of a@readonlyannotation - which exists as something to be examined in a different tutorial (Learn how to read annotations in OData metadata documents, part of the OData Deep Dive mission). We will ignore this for the purposes of this tutorial.Talking of “out of the box”, the CAP server by default, in local development mode, uses the mocked authentication strategy.
👉 Check the detail of that, by looking at the effective configuration, specifically for the
authsection:shellCopycds env requires.authThis should emit something like this:
javascriptCopy{ restrict_all_services: false, kind: 'mocked', users: { alice: { tenant: 't1', roles: [ 'admin' ] }, bob: { tenant: 't1', roles: [ 'cds.ExtensionDeveloper' ] }, carol: { tenant: 't1', roles: [ 'admin', 'cds.ExtensionDeveloper' ] }, dave: { tenant: 't1', roles: [ 'admin' ], features: [] }, erin: { tenant: 't2', roles: [ 'admin', 'cds.ExtensionDeveloper' ] }, fred: { tenant: 't2', features: [ 'isbn' ] }, me: { tenant: 't1', features: [ '*' ] }, yves: { roles: [ 'internal-user' ] }, '*': true }, tenants: { t1: { features: [ 'isbn' ] }, t2: { features: '*' } } }👉 Observe:
- in this mode, there are no built-in restrictions on any of the services by default (see the blog post “CAP service authentication at design time and in production” for more on this)
- the authentication strategy (
kind) is indeed “mocked” - there are some sample users, with various tenant and role assignments, that we can use
👉 Start up a CAP server for this project with
DEBUG=basic cds watchand observe the output, which should include:logCopy[cds] - loaded model from 2 file(s): srv/main.cds db/schema.cds [cds] - using bindings from: { registry: '~/.cds-services.json' } [cds] - connect to db > sqlite { url: ':memory:' } > init from db/data/northbreeze-Suppliers.csv > init from db/data/northbreeze-Products.csv > init from db/data/northbreeze-Categories.csv /> successfully deployed to in-memory database. [cds] - using auth strategy { kind: 'mocked' } [cds] - serving Main { at: [ '/northbreeze' ], decl: 'srv/main.cds:4' } [cds] - server listening on { url: 'http://localhost:4004' }Note that use of the mocked authentication strategy is indeed announced.
The
mockedauthentication strategy is more or less just thebasicauthentication strategy, with these sample users, so we ask for debug level output for thebasicmodule here (as there isn’t any specificmockeddebug output). - Step 3
In this step you’ll explore a couple of auth related aspects of the service as it stands right now.
👉 First, create a file
Explore.httpin a new directorytest/within the project root, with the following content:textCopy@server=http://localhost:4004 ### List first three products GET {{server}}/northbreeze/Products?$top=3&$select=ProductName ### Delete product 1 (Chai) DELETE {{server}}/northbreeze/Products/1These are HTTP requests in a format that can be understood and executed by the REST Client extension for VS Code (and in Codespaces), an extension that is included in the set defined for the Dev Container that is in use here.
You can generate such HTTP requests in this format with the
cds add httpcommand too.👉 Ensure that the CAP server is still running, and execute first the
GETrequest, then theDELETErequest, via the selectable “Send Request” text that appears above each one in the editor.The
GETrequest should return a 200 response with three products Chai, Chang and Aniseed Syrup. TheDELETErequest should return a 204 response with no content. If you were to execute the sameGETrequest again, you would get the products Chang, Aniseed Syrup and Chef Anton’s Cajun Seasoning, as Chai (product with ID 1) is now gone.Take a note of the implications here (again, ignoring the
@readonlyannotation for theCategoriesinsrv/main.cds):- we are able to access the service and resources within it without identifying ourselves (no authentication)
- we have full read-write access to the resources within the service (no authorization restrictions)
- Step 4
Let’s add a requirement for clients to identify themselves in the requests they send. In other words, add an authentication requirement.
Specify the pseudo-role
authenticated-useras a requirement at the service level, by adding an annotation tosrv/main.cdsso it looks like this:cdsCopyusing northbreeze from '../db/schema'; @path : '/northbreeze' @requires: 'authenticated-user' service Main { ... }👉 Once the CAP server restarts after this change, try the
GETandDELETErequests again.This time, the response code is 401, stating that the request has not been authenticated, i.e. no (valid) credentials have been provided. In fact, no credentials were provided at all, so this is the response we want.
There is a debate about the HTTP status text that accompanies codes 401 and 403. Some argue (with good reason) that the text “Unauthorized” with 401 is misleading, as that really is the text that should accompany 403, and that “Unauthenticated” should be the text to accompany 401. Digging into that debate is an exercise left for you, dear reader.
The debug log message that appears in the CAP server output is helpful here:
logCopy[basic] - 401 > login required - Step 5
👉 Provide authentication details, using one of the sample users, by adding
@usernameand@passwordreferences, andAuthenticationheaders to both requests intest/Explore.http, as shown here:textCopy@server=http://localhost:4004 @username=alice @password= ### Products GET {{server}}/northbreeze/Products?$top=3&$select=ProductName Authorization: Basic {{username}}:{{password}} ### Products DELETE {{server}}/northbreeze/Products/1 Authorization: Basic {{username}}:{{password}}With the sample users, there is no password, as it wouldn’t make much sense, as passwords are not what we’re concerned about here, it’s authentication and authorization.
👉 Retry each request again, and this time observe:
- the requests are successful
- the authentication provided in those requests is logged in the debug output:
logCopy
[basic] - authenticated: { user: 'alice', tenant: undefined, features: undefined }
- Step 6
Let’s go deeper and more granular now, and introduce a further restriction where the authenticated user requires a specific role
productmanager. Note that right now, the sample useraliceonly has a single roleadmin.👉 Specify a
@restrictannotation for theProductsprojection in the service insrv/main.cds, as shown here:cdsCopyusing northbreeze from '../db/schema'; @path : '/northbreeze' @requires: 'authenticated-user' service Main { @restrict: [{ grant: 'WRITE', to : 'productmanager' }] entity Products as projection on northbreeze.Products; ... } - Step 7
👉 Once the CAP server has restarted again, try the
DELETErequest, and observe what happens, which is:- a 403 response is returned, indicating insufficient authorization
- this in turn implies that authentication was indeed successfully provided
In other words, “you’ve identified yourself, but you don’t have the authorization for the request you wish to make”. The missing authorization in this case is the role
productmanager. - Step 8
👉 Before adding the
productmanagerrole to the sample useralice, have a go at theGETrequest too, and observe what happens here:- a 403 response is also returned for this read-only request, which previously had been successful!
This is because a request is only allowed through “if at least one of the privileges is met” - and there are no privileges that allow for read operations.
- Step 9
👉 To remedy this, add a second privilege to the array for the
@restrictannotation so it looks like this:cdsCopyusing northbreeze from '../db/schema'; @path : '/northbreeze' @requires: 'authenticated-user' service Main { @restrict: [ { grant: 'READ', to : 'any' }, { grant: 'WRITE', to : 'productmanager' } ] entity Products as projection on northbreeze.Products; ... }👉 After the CAP server restarts, retry the
GETrequest, which should now succeed. - Step 10
The sample users and roles that come with the mocked authentication strategy are not static, they can be built upon.
👉 Create a file
.cdsrc.yamlin the project root, with the following content:yamlCopycds: requires: auth: users: alice: roles: - admin - productmanagerThe
.cdsrc.yamlfile is one of many sources for the effective CDS configuration for the project.👉 Stop the CAP server, and check the effective configuration, specifically the details for the user
alice, like this:shellCopycds env requires.auth.users.aliceThis should show that the user now has both
adminandproductmanagerroles:javascriptCopy{ tenant: 't1', roles: [ 'admin', 'productmanager' ] } - Step 11
👉 Start the CAP server, again with
DEBUG=basic cds watch, and retry theDELETErequest from within thetest/Explore.httpfile.This time, observe:
- the request is successful, with a 204 No Content response, as expected
- there’s a line in the CAP server’s log output that confirms it was indeed
alicethat was authenticated:logCopy[basic] - authenticated: { user: 'alice', tenant: undefined, features: undefined }
Well done!
- Step 12
For further info, refer to these resources:
- Feature definition FEA002 Mocking auth
- Blog post Local-first dev with CAP Node.js - mocking auth
- Blog post OData Deep Dive rewrite in the open
- The CAP Project Explorer was released with cds 10 in June 2026
- Blog post CAP service authentication at design time and in production
- Details for the cds add http command in Capire
- Info on Authentication Strategies and Pseudo Roles in Capire
- A list of sources for cds.env in Capire
According to the Capire documentation section on Authentication Strategies, what is the default strategy used in production?
- Set up a simple Northwind-based service
- Examine the service context
- Explore the service as-is
- Introduce an authentication requirement
- Retry the requests with authentication
- Apply more granular authorization restrictions
- Retry the DELETE request
- Retry the GET request
- Include an explicit privilege block for read operations
- Add the required role to the user
- Retry the DELETE request once again
- Wrap-up and further info