label | synopsis | status | uacp |
---|---|---|---|
Authentication |
This guide is about authenticating users on incoming HTTP requests.
|
released |
This page is linked from the Help Portal at https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/29c25e504fdb4752b0383d3c407f52a6.html |
{{$frontmatter?.synopsis}} This is done by authentication middlewares setting the req.user
property which is then used in authorization enforcement decisions.
Represents the currently logged-in user as filled into req.user
by authentication middlewares.
Simply create instances of cds.User
or of subclasses thereof in custom middlewares.
For example:
const cds = require('@sap/cds')
const DummyUser = new class extends cds.User { is:()=>true }
module.exports = (req,res,next) => {
req.user = new DummyUser('dummy')
next()
}
Or you can call the constructor of cds.User
with specific arguments, to create a user instance. For example:
const cds = require('@sap/cds')
// with user ID as string
const user = new cds.User('userId')
// a user instance
const anotherUser = new cds.User(user)
// a user instance like object
const yetAnotherUser = new cds.User({id: user.id, roles: user.roles, attr: user.attr})
A user's unique ID.
It corresponds to $user
in @restrict
annotations of your CDS models
(Also in JavaScript, user
can act as a shortcut for user.id
in comparisons.) {.indent}
Checks if user has assigned the given role. Example usage:
if (req.user.is('admin')) ...
The role names correspond to the values of @requires
and the @restrict.grants.to
annotations in your CDS models.
User-related attributes, for example, from JWT tokens
These correspond to $user.<x>
in @restrict
annotations of your CDS models {.indent}
Use req/msg.tenant
instead.{.learn-more}
Use req/msg.locale
instead.{.learn-more}
In some cases, you might need to bypass authorization checks while consuming a local service. For this, you can create a transaction with a privileged user as follows:
this.before('*', function (req) {
const user = new cds.User.Privileged
return this.tx({ user }, tx => tx.run(
INSERT.into('RequestLog').entries({
url: req._.req.url,
user: req.user.id
})
)
})
Alternatively, you can also use the ready-to-use instance cds.User.privileged
directly, i.e., const user = cds.User.privileged
.
Class cds.User.Anonymous
allows you to instantiate an anonymous user (const user = new cds.User.Anonymous
), for example in a custom authentication implementation.
Alternatively, you can also use the ready-to-use instance cds.User.anonymous
directly, i.e., const user = cds.User.anonymous
.
If a request couldn't be authenticated, for example due to a missing authorization header, the framework will use cds.User.default
as fallback.
By default, cds.User.default
points to cds.User.Anonymous
. However, you can override this, for example to be cds.User.Privileged
in tests, or to be any other class that returns an instance of cds.User
.
Applications can use the req.user
APIs to do programmatic enforcement.
For example, the authorization of the following CDS service:
service CustomerService @(requires: 'authenticated-user'){
entity Orders @(restrict: [
{ grant: ['READ','WRITE'], to: 'admin' },
]){/*...*/}
entity Approval @(restrict: [
{ grant: 'WRITE', where: '$user.level > 2' }
]){/*...*/}
}
can be programmatically enforced by means of the API as follows:
const cds = require('@sap/cds')
cds.serve ('CustomerService') .with (function(){
this.before ('*', req =>
req.user.is('authenticated') || req.reject(403)
)
this.before (['READ', 'CREATE'], 'Orders', req =>
req.user.is('admin') || req.reject(403)
)
this.before ('*', 'Approval', req =>
req.user.attr.level > 2 || req.reject(403)
)
})
CAP ships with a few prebuilt authentication strategies, used by default: mocked
during development and jwt
in production.
You can override these defaults and configure the authentication strategy to be used through the cds.requires.auth
config option in cds.env
, for example:
::: code-group
"cds": {
"requires": {
"auth": "jwt"
}
}
:::
::: tip Inspect effective configuration
Run cds env get requires.auth
in your project root to find out the effective config for your current environment.
:::
This strategy creates a user that passes all authorization checks. It's meant for temporarily disabling the @requires
and @restrict
annotations at development time.
Configuration: Choose this strategy as follows:
::: code-group
"cds": {
"requires": {
"auth": "dummy"
}
}
:::
This authentication strategy uses basic authentication with pre-defined mock users during development.
Note: When testing different users in the browser, it's best to use an incognito window, because logon information might otherwise be reused.
Configuration: Choose this strategy as follows:
::: code-group
"cds": {
"requires": {
"auth": "mocked"
}
}
:::
You can optionally configure users as follows:
::: code-group
"cds": {
"requires": {
"auth": {
"kind": "mocked",
"users": {
"<user.id>": {
"password": "<password>",
"roles": [ "<role-name>", ... ],
"attr": { ... }
}
}
}
}
}
:::
The default configuration shipped with @sap/cds
specifies these users:
"users": {
"alice": { "tenant": "t1", "roles": [ "cds.Subscriber", "admin" ] },
"bob": { "tenant": "t1", "roles": [ "cds.ExtensionDeveloper", "cds.UIFlexDeveloper" ] },
"carol": { "tenant": "t1", "roles": [ "cds.Subscriber", "admin", "cds.ExtensionDeveloper", "cds.UIFlexDeveloper" ] },
"dave": { "tenant": "t1", "roles": [ "cds.Subscriber", "admin" ], "features": [] },
"erin": { "tenant": "t2", "roles": [ "cds.Subscriber", "admin", "cds.ExtensionDeveloper", "cds.UIFlexDeveloper" ] },
"fred": { "tenant": "t2", "features": [ "isbn" ] },
"me": { "tenant": "t1", "features": [ "*" ] },
"yves": { "roles": [ "internal-user" ] }
"*": true //> all other logins are allowed as well
}
This default configuration is merged with your custom configuration such that, by default, logins by alice, bob, ... and others (*
) are allowed.
If you want to restrict these additional logins, you need to overwrite the defaults:
"users": {
"alice": { "roles": [] },
"bob": { "roles": [] },
"*": false //> do not allow other users than the ones specified
}
This authentication strategy uses basic authentication to use mock users during development.
Note: When testing different users in the browser, it's best to use an incognito window, because logon information might otherwise be reused.
Configuration: Choose this strategy as follows:
::: code-group
"cds": {
"requires": {
"auth": "basic"
}
}
:::
You can optionally configure users as follows:
::: code-group
"cds": {
"requires": {
"auth": {
"kind": "basic",
"users": {
"<user.id>": {
"password": "<password>",
"roles": [ "<role-name>", ... ],
"attr": { ... }
}
}
}
}
}
:::
In contrast to mocked authentication, no default users are automatically added to the configuration.
This is the default strategy used in production. User identity, as well as assigned roles and user attributes, are provided at runtime, by a bound instance of the 'User Account and Authentication' service (UAA). This is done in form of a JWT token in the Authorization
header of incoming HTTP requests.
Prerequisites: You need to add passport to your project:
npm add passport
Prerequisites: You need to add @sap/xssec to your project:
npm add @sap/xssec
Configuration: Choose this strategy as follows:
::: code-group
"cds": {
"requires": {
"auth": "jwt"
}
}
:::
Learn more about testing JWT-based authentication in XSUAA in Hybrid Setup.{.learn-more}
Authentication kind xsuaa
is a logical extension of kind jwt
that additionally offers access to SAML attributes through req.user.attr
(for example, req.user.attr.familyName
).
Prerequisites: You need to add passport to your project:
npm add passport
Prerequisites: You need to add @sap/xssec to your project:
npm add @sap/xssec
Configuration: Choose this strategy as follows:
::: code-group
"cds": {
"requires": {
"auth": "xsuaa"
}
}
:::
See XSUAA in Hybrid Setup below for additional information of how to test this{.learn-more}
::: warning It's recommended to only use this authentication kind if it's necessary for your use case, as it denotes a lock-in to SAP BTP. :::
This is an additional authentication strategy using the Identity Authentication Service (IAS) that can be used in production. User identity and user attributes are provided at runtime, by a bound instance of the IAS service. This is done in form of a JWT token in the Authorization
header of incoming HTTP requests.
To allow forwarding to remote services, JWT tokens issued by IAS service don't contain authorization information. In particular, no scopes are included. Closing this gap is up to you as application developer.
Prerequisites: You need to add passport to your project:
npm add passport
Prerequisites: You need to add @sap/xssec to your project:
npm add @sap/xssec
Configuration: Choose this strategy as follows:
::: code-group
"cds": {
"requires": {
"auth": "ias"
}
}
:::
You can configure an own implementation by specifying an own impl
as follows:
"requires": {
"auth": {
"impl": "srv/custom-auth.js" // > relative path from project root
}
}
Essentially, custom authentication middlewares must do two things:
First, they must fulfill the req.user
contract by assigning an instance of cds.User
or a look-alike to the incoming request at req.user
.
Second, if running in a multitenant environment, req.tenant
must be set to a string identifying the tenant that is addressed by the incoming request.
module.exports = function custom_auth (req, res, next) {
// do your custom authentication
req.user = new cds.User({
id: '<user-id>',
roles: ['<role-a>', '<role-b>'],
attr: {
<user-attribute-a>: '<value>',
<user-attribute-b>: '<value>'
}
})
req.tenant = '<tenant>'
}
If you want to customize the user ID, please also have a look at this example.{.learn-more}
In a productive scenario with an authentication strategy configured, for example the default jwt
, all CAP service endpoints are authenticated by default, regardless of the authorization model. That is, all services without @restrict
or @requires
implicitely get @requires: 'authenticated-user'
.
This can be disabled via feature flag cds.env.requires.auth.restrict_all_services: false
, or by using mocked authentication explicitly in production.
The following steps assume you've set up the Cloud Foundry Command Line Interface.
- Log in to Cloud Foundry:
cf l -a <api-endpoint>
If you don't know the API endpoint, have a look at section Regions and API Endpoints Available for the Cloud Foundry Environment.
-
Go to the project you have created in Getting started in a Nutshell.
-
Configure your app for XSUAA-based authentication if not done yet:
cds add xsuaa --for hybrid
This command creates the XSUAA configuration file xs-security.json
and adds the service and required dependencies to your package.json
file.
- Make sure
xsappname
is configured andtenant-mode
is set todedicated
inxs-security.json
file:
{
"xsappname": "bookshop-hybrid",
"tenant-mode": "dedicated",
...
}
-
Configure the redirect URI:
Add the following OAuth configuration to the
xs-security.json
file:"oauth2-configuration": { "redirect-uris": [ "http://localhost:5001/" ] }
-
Create an XSUAA service instance with this configuration:
cf create-service xsuaa application bookshop-uaa -c xs-security.json
Later on, if you've changed the scopes, you can use
cf update-service bookshop-uaa -c xs-security.json
to update the configuration.::: tip This step is necessary for locally running apps and for apps deployed on Cloud Foundry. :::
-
Create a service key:
cf create-service-key bookshop-uaa bookshop-uaa-key
You do this, to gain access to the XSUAA credentials from your local application.
-
Bind to the new service key:
cds bind -2 bookshop-uaa
This adds an
auth
section containing the binding and the kindxsuaa
to the .cdsrc-private.json file. This file is created if it doesn't exist and keeps the local and private settings of your app:{ "requires": { "[hybrid]": { "auth": { "kind": "xsuaa", "binding": { ... } } } } }
If your running in BAS, you can alternatively create a new run configuration, connecting the
uaa
to your XSUAA service instance.In that case you need to add the environment variable
cds_requires_auth_kind=xsuaa
to the run configuration. -
Check authentication configuration:
cds env list requires.uaa --resolve-bindings --profile hybrid
This prints the full uaa
configuration including the credentials.
By creating a service instance of the xsuaa
service, all the roles from the xs-security.json file are added to your subaccount. Next, you create a role collection that assigns these roles to your users.
-
Open the SAP BTP Cockpit.
For your trial account, this is: https://cockpit.hanatrial.ondemand.com
-
Navigate to your subaccount and then choose Security > Role Collections.
-
Choose Create New Role Collection:
-
Enter a Name for the role collection, for example
BookshopAdmin
, and choose Create. -
Choose your new role collection to open it and switch to Edit mode.
-
Add the
admin
role for your bookshop application (application idbookshop!a<XXXX>
) to the Roles list. -
Add the email addresses for your users to the Users list.
-
Choose Save
The approuter component implements the necessary authentication flow with XSUAA to let the user log in interactively. The resulting JWT token is sent to the application where it's used to enforce authorization and check the user's roles.
-
Add approuter in the
app
folder of your project:cds add approuter
-
Install
npm
packages for approuter:npm install --prefix app
-
In your project folder run:
::: code-group
cds bind --exec -- npm start --prefix app
cds bind --exec -- npm start --prefix app
cds bind --exec '--' npm start --prefix app
:::
Learn more about
cds bind --exec
.{.learn-more}This starts an approuter instance on http://localhost:5001 with the credentials for the XSUAA service that you have bound using
cds bind
.Usually the approuter is started using
npm start
in theapp
folder. But you need to provide theVCAP_SERVICES
variable with the XSUAA credentials. With thecds bind --exec
command you can launch an arbitrary command with theVCAP_SERVICES
variable filled with yourcds bind
service bindings.Since it only serves static files or delegates to the backend service, you can keep the server running. It doesn't need to be restarted after you have changed files.
-
Make sure that your CAP application is running as well with the
hybrid
profile:cds watch --profile hybrid
If you are using BAS Run Configurations, you need to configure
cds watch
with profilehybrid
:- Right click on your run configuration
- Choose Show in File
- Change the command
args
:
"args": [ "cds", "watch", "--profile", "hybrid" ],
-
After the approuter and CAP application are started, log in at http://localhost:5001 and verify that the routes are protected as expected.
In our example, if you assigned the
admin
scope to your user in SAP BTP cockpit, you can now access the admin service at http://localhost:5001/admin.
To test UIs w/o a running UAA service, just add this to xs-app.json:
"authenticationMethod": "none"
SAP Business Application Studio:
The login fails pointing to the correct OAuth configuration URL that is expected.
-
Replace the URL
http://localhost:5001/
in yourxs-security.json
file with the full URL from the error message:"oauth2-configuration": { "redirect-uris": [ "<url from error message>" ] }
::: warning This is a specific configuration for your dev space and should not be submitted or shared. :::
-
Update the XSUAA service:
cf update-service bookshop-uaa -c xs-security.json
-
Retry