Build a User Management Microservice in BTP with CAP
NOTE: this blog post is intended for developers who have previous experience in developing CAP applications using SAP Business Application Studio, SAP BTP destinations, and the destination and XSUAA services.
Secure cloud software should always rely on some sort of authentication and authorization mechanism to let users benefit from its functionality and protect it from attackers and/or malicious usage.
Based on such mechanism, users must first authenticate using login and password (safer systems even use two-factor authentication with some extra identity check technique, such as security tokens), so they can prove their identity, and, then, the application checks whether they are authorized to use its functionality and, if yes, which functionality – from the whole application – they are allowed to use: this is basically the definition of authentication (first part) and authorization (second part).
Authorizations are provided through some role-based mechanism through which a set of “roles” are assigned to users, granting them authorization to perform operations in the system (i.e. create, read, and/or update some data). So, a user with a role of, let’s say, “Viewer” would be allowed to access data in “read-only mode”, whilst a user with a role of “Admin” would also be allowed to write data to the system.
Therefore, secure cloud business applications must have the ability to let their administrators manage their business users, defining who can authenticate to the application and what access level each one has (which functionality can be used by each user)..
The Business Problem
In BTP, users are authenticated through some Identity Provider (IdP) that has a trust relationship with each subaccount. This grants access to the platform landscape, but is usually not enough to authorize them to use the hosted business applications as most of them will have their own set of “authorizations” to control who can do what within the software.
Such authorizations are granted via the so-called “role collections” – a set of roles containing “scopes” that allow users to execute specific tasks in the application. To assign those role collections to the users in the platform, BTP requires to have such users kind of “replicated” in its own “user store” (instead of the one from the IdP) as the role collections are also stored in BTP – those “replicated” users are called “shadow users”.
But, then, what is the problem with that? Well, all of that stuff is managed either via BTP cockpit or CLI and not actually by the business application itself, meaning that a BTP subaccount administrator has to do it (create and/or update shadow users – if not done automatically upon authentication by the IdP – assign and/or remove role collections, etc.). Also, the BTP user store can have lots of shadow users accessing different business applications which makes it even more difficult to the subaccount administrator to manage.
So, why not let the business application itself manage its own users by “filtering” from the BTP user store only those assigned to specific role collections – the ones belonging to the application? That’s the recurring question which’s been raised in most of the technical advisory sessions I’ve been providing to developers at SAP partners and is exactly the motivation for this blog post.
In this blog post, we will understand the key-points of a CAP microservice which’s been developed to provide user management functionality to any generic business application.
The scenario in this post is intended to deep dive into a fully working CAP microservice leveraging an SAP Fiori UI for testing. The service manages users who are assigned to specific role collections (the ones belonging to the business application that is consuming the service) reading and writing user information from BTP using its XSUAA service APIs. The microservice is ready to deploy on BTP, Cloud Foundry Runtime.
Here’s a diagram representing the overall architecture of the microservice:
Now, let’s analyze this architecture: first we have SAP Business Technology Platform with the Cloud Foundry Environment and the Authorization and Trust Management Service (XSUAA).
XSUAA manages the BTP user store (namely “Shadow Users”). In Cloud Foundry we deploy the CAP microservice which will be responsible for managing the business application’s users. This microservice will “filter” users from BTP based on a set of role collections belonging to the business application.
It interacts with the XSUAA service through a destination to make REST API calls to the services’ user management APIs which are based on the System for Cross-Domain Identity Management (SCIM) standard.
A simple Fiori Elements HTML5 application has been built to serve as a UI for the microservice and the resulting application could be optionally published to a Launchpad Service site.
Actually, that application is already built using SAP Cloud Application Programming Model (CAP) and BTP’s XSUAA APIs. The full code can be found in this GitHub repo from SAP samples.
As previously mentioned, the intent of this blog post is just to walk through and understand the key-points of the development.
Therefore, as first prerequisite to follow-up with this post, you must clone the repo strictly following the instructions from its README.md to setup the project locally.
NOTE: do not miss any single step of the instructions, otherwise you won’t be able to run and deploy the application.
Additionally, before going deeper into the contents of this post, please read through these three blog posts:
- Fundamentals of Security in SAP BTP
- Fundamentals of Security in BTP: What is OAuth and how does it work?
- Fundamentals of Security in BTP: Implement Authentication and Authorization in a Node.js App
- Run Node.js Applications with Authentication Locally
Security Descriptor (xs-security.json)
The security descriptor (xs-security.json) is the file which defines the details of the authentication methods and authorization types to use for access to your application.
The xs-security.json file uses JSON notation to define the security options for an application. The information in this file is used at application-deployment time, for example, to create required roles for your application.
In the descriptor, we define the application name, tenant mode (dedicated = single-tenant) and description.
Next we define a collection of application scopes (each single task that a user can execute in the application) and reference them in a list of role templates. The granularity level of the scopes is defined by the application developer. In this simple example, we are working with higher granularity: just administrative tasks, user management tasks and regular user tasks.
The first scope and corresponding role template are used in the OAuth flow executed by the application’s router (namely AppRouter). They are just added to the JWT (JSON Web Token) to signal that the user is authenticated and authorized to access the application. The other scopes are really the ones which define what each user can do.
Next, we group the role templates into role collections which are actually assigned to the business users.
Finally, we configure the allowed URIs for the OAuth flow when it’s redirecting the user after authentication, and the JWT has been properly issued (basically the BTP and Business Application Studio domains).
To learn more about the syntax of the XSUAA security descriptor you can read this official document.
The reference to determine which services will be required by a certain application is always its defined architecture. So, looking at the architecture we can immediately infer that:
- The microservice will access the XSUAA APIs using a destination, hence an instance of the destination service must be created and bound to it (via some service key).
- To secure the application itself, the security configuration from the descriptor must be applied and managed by XSUAA. So, an instance of the authorization and trust management service for the application (application plan) must be created and bound to it (via some service key).
- Finally, the microservice must call the user management API from XSUAA. Therefore, another instance of the authorization and trust management service to access APIs (apiaccess plan) must also be created. But this one, is not bound to the application: we just create a service key and use it to configure the destination in BTP.
The binding of the first two service instances to the CAP project is done via emulation of the VCAP_SERVICES in the default-env.json file as instructed in the git repo. The binding is also done when deploying to BTP Cloud Foundry as per the definitions in the mta.yaml file.
We look into the XSUAA apiaccess service key and use some relevant information from it to configure the destination like demonstrated below:
For this development, some adjustments are required to the projects’ package.json file.
As the microservice itself do not require persistence, because the users are actually persisted in BTP’s user store, we will use the SQLite in-memory database in production to replicate the users’ information in the microservice’s memory. By doing so, we hand over all the OData v4 handling stuff to the CAP framework. Therefore, SQLite must be moved from devDependencies to Dependencies.
To avoid deployment issues with the Cloud Foundry NodeJS Buildpack, we explicitly define the node and npm engines versions.
Next, we create the cds.requires section containing:
- An authentication block with a mock user for the development profile, making sure it has the “UserAdmin” role assigned;
- A DB block defining the usage of a SQLite in-memory database;
- An external service block pointing to the XSUAA REST API via the previously created destination;
- And, finally, a UAA block to specify that XSUAA will be used to secure the microservice;
The last section to add is “features”, where we explicitly specify that an in-memory database will be used in production.
To make the service totally flexible and decoupled from the rest of the business application, we use two environment variables to configure it:
- APP_AUTHS: a list of JSON objects separated by a “|” (pipe) character – each object represents an authorization (namely role collection) for the business application user.
- DEFAULT_AUTH: the ID of the authorization object that’s used as default when no authorization is assigned to a user.
During development those variables are provided in the project’s .env configuration file. In production, after the service is deployed, they are set with cf set-env followed by a cf restage.
Here are the details of the data model entities, defined under the user.mngr.db namespace:
- The User entity with some selected properties from the BTP user store. Each user is associated to an origin IdP and is composed by one or several authorizations (assigned role collections).
- Such composition is defined into the UserAuthorization entity whose parent is the User.
- The BTP subaccount trusted IdPs are also retrieved using the XSUAA REST API
- And the authorizations (role collections) are populated using the APP_AUTHS environment variable set in the .env file (and later in production).
Here we write the service definition under the user.mngr.srv namespace, referencing user.mngr.db as the model. We name the service UserMngrService defining the endpoint as ‘user-mngr’ and granting access only to users with the UserAdmin role (scope).
Then we expose the User entity as a projection from the model and attach a collection action (meaning it operates over an entire entity collection) which will be used to reload the data from BTP.
This serves to keep the service synchronized with BTP’s user store in the case of changes done directly in BTP via cockpit.
Annotations for the UI
To get the service prepared for being used in a Fiori UI, we annotate its entities with UI annotations.
Please, feel free to explore the service-ui.cds annotations file by yourself. This git repo from SAP samples has a really nice reference and showcase of annotations to help you understand the full content.
In this post, we’ll focus only on the most relevant annotations for the best UI behavior.
When the “refresh” action is executed, as a side effect, the corresponding list report table must be reloaded:
In the same way, when first and last names are filled, the “display name” (conjunction of first and last names) must be updated in the UI:
The “origin” property from the User entity must be filled from a list of trusted IdPs:
And the key of such IdP, which is used in the association, must display the IdP name instead of the key value itself:
The “authorization” property of the UserAuthorization composition must also be filled from a list of authorizations:
Whenever an authorization is selected to fill the property, the Authorization entity is affected and, as a side effect, the “authorization description” must be updated in the UI:
Finally, the “authorization ID” must also display the authorization name, instead of the ID value itself:
The microservice logic goes into the service handlers. In this project the code is organized into a module (/srv/lib/handlers.js) for better maintainability.
The code logic can be easily understood by reading the comments spread all around it – which are pretty much self-explanatory. Therefore, in this post we’ll focus exclusively on the key parts responsible for interacting with the XSUAA APIs.
Upon module load, we connect to the XSUAA service destination to retrieve an HTTP client called “xsuaa”:
And this is how the client is used:
1. Read users from BTP:
2. Create a user in BTP:
3. Assign role collections to a user:
4. Remove role collections from a user:
6. Read the list of trusted IdPs:
The handler functions in the module are used by the service module (/srv/service.js) to attach them to their respective events (after, before, on, etc.).
And this concludes the analysis and explanation of the user management microservice CAP project.
Here’s a list of resources to enhance your learning experience on this topic:
- Application Security Descriptor Configuration Syntax
- Create a Destination in the SAP BTP Cockpit
- Project-Specific Configurations
- Domain Modelling with CDS
- Providing Services
- Serving Fiori UIs
- User Management: System for Cross-Domain Identity Management (SCIM)
- Managed Application Router
- Adding Fiori Apps to CAP Projects
- Deploy to Cloud Foundry
User management is a key topic to secure cloud native applications. After setting up the project from this git repo and going through this blog post content, you’ll have learnt and experimented how to build a CAP microservice which will allow your business application to manage its business users autonomously. Hope you have enjoyed the journey!
UPDATE: if you want to implement a multi-tenant version of this microservice, please read through this blog post.
Please, do not hesitate to submit your questions in Q&A in SAP Community: https://answers.sap.com/index.html