Technical Articles
How to get authenticated user information with CAP in three different ways – Using the CAP request object
Prerequisite
To follow-up with this blog post you must have read and completed the post: Setup project for development.
Modify the “userInfo” Function Handler
The second approach to get the authenticated user information is also simple, but is actually done in the backend service of the solution, meaning the CAP service.
As we have previously prepared a “skeleton” to implement the code for this approach (via the userInfo function), now we just need to replace the ‘return “”;‘ with the appropriate code.
In this approach we will just return the information that is available in an attribute of the CAP’s request object (req). This attribute is the “user” object of class cds.User.
To learn more about this object you can refer to this page from the CAP official documentation.
Now, open-up the user-info-service.js file, and modify the code like demonstrated below:
NOTE: if you already completed the last approach (using the XSUAA API) modify only the userInfo implementation.
module.exports = cds.service.impl(async function () {
// using req.user approach (user attribute - of class cds.User - from the request object)
this.on('userInfo', req => {
const user = {
id : req.user.id,
tenant : req.user.tenant,
_roles: req.user._roles,
attr : req.user.attr
}
return user;
});
// using the XSUAA API
this.on('userInfoUAA', async () => {
return "";
});
});
Adjust the Index Page
The code is executed by accessing the “/user-info/userInfo()” endpoint. So, let’s just set it as a link in the index.html page by modifying this line:
<li><a href="/user-info/userInfo()">2. Using the CAP request object</a></li>
By doing so, your index.html file should now look like this:
Figure 1 – New link in index.html
NOTE: if you haven’t completed the post for the first approach (directly from the HTML5 app) your index.html file might look slightly different (no links for the first approach).
Test the Approach
As previously mentioned this approach relies on the backend service. Therefore, the service must be running for it to work properly.
NOTE: you might have already executed the steps to split the Terminal if you completed the post for the last approach (using the XSUAA API), so you may skip them.
In the Terminal, let’s make sure to be positioned in the project root folder (“user-info“) – if not already there. If you where running the first approach you might have ended up in the “app” folder after pressing CTRL-C, so just go back with: cd ..
Click on the prompt in the current opened Terminal, then in the top menu of Business Application Studio click on Terminal and select Split Terminal:
Figure 2 – Terminal menu
Now, your Terminal window, should have been split into two like demonstrated below:
Figure 3 – Split Terminal
Let’s first start the backend service. In the first Terminal run the command:
cds watch --profile hybrid
This should be the expected outcome:
Figure 4 – Backend service started
In the second Terminal move to the app directory (cd app) and run the command:
npm run start
This should be the expected outcome:
Figure 5 – AppRouter started
And, after a few seconds, you should see a pop-up in the bottom-right corner of Business Application Studio with a button to open the application in a new tab:
Figure 6 – Pop-up to open app in new tab
Click on that button to access the application’s index page: at this point the AppRouter will execute the OAuth 2.0 authentication flow (login) and display the index.html page:
Figure 7 – Application index page
NOTE: if you haven’t completed the post for the first approach (directly from the HTML5 app) that page might look slightly different (no links for the first approach).
Click on the “2. Using the CAP request object” link (the /user-info/userInfo() endpoint) and the user information should be displayed in JSON format like demonstrated below:
Figure 8 – Information available in the req.user object
The “id” attribute is the “username” utilized for login. Notice that this object also includes the “tenant” attribute (tenant GUID) which is useful for handling operations in a multi-tenant application. It also includes a “_roles” collections that is meant exclusively for internal usage by the framework. To check whether the user has an specific role (that is XSUAA scope) assigned you can use the method “is” (i.e. req.user.is(‘admin’). The user’s “attributes” are also available and can be accessed in the form of req.user.attr.<x>.
However, this object lacks some other information, like e-mail, first and last names, which can be found in the response from the User API Service (first approach).
Again, you can deep dive into this object by reading this page from the CAP official documentation.
Conclusion
After having gone through the steps of this blog post you should have successfully fetched authenticated user information in the backend service leveraging the user attribute (property) from the request object of the CAP framework. The next step would be to try one of the other different approaches proposed in this blog posts series (if not yet done).
Please, do not hesitate to submit your questions in SAP Community through the Q&A tag link: https://answers.sap.com/index.html
Hello Alessandro,
great blog.
I've tested it, and I get:
I cross-checked every file and all are correct.
Do you have a hint, where to look, if the "value" attribute is empty.
Regards,
Harry Fischer.
Haven't you linked the wrong function to the indext.html link? Notice that one of them is returning "" which means an empty value attribute.
Another possibility is that you're not properly logged in through the approuter, thus req.user might be empty as well.
Hello Alessandro,
thank you for your quick anwer.
I guess "req.user" cannot be received. If I request a sub object of req.user all works fine.
It works also for "id", "attr".
Another topic:
I want to receive the scopes. "req.user._scopes" or "req.user.scopes" is not working. The CAP docu does not have a answer either.
Question: How can I get the scopes or get the info for a specific scope, i.e. req.user.hasScope('MyScope')?
Just noticed that your issue is due to changes in newer versions of the @sap/cds node module (released some months after this blog series has been published). I did the appropriate adjustments in the code to reflect the current framework behavior. Thank you very much for such heads-up!
As per the scopes, the cds.User class does not have such property, but given what's explained in the CAP documentation: "The role names correspond to the values of @requires and the @restrict.grants.to annotations in your CDS models." and interpreting the definitions we usually place in the xs-security.json descriptor (which configures XSUAA for each application) we can assume that whatever is returned into the _roles collection may correspond to the application's scopes.
Hello Alessandro,
>>As per the scopes, the cds.User class does not have such property,
yes I know the CAP dokumentation. It seems it has some limitations compared to java.
I can dodge the problem, if I create a role for each scope in xs-security.json.
Just one thing:
I have enhanced the xs-security.json with my own scopes, atrributes and roles.
Updated the service with "cf update-service user-info-xsuaa -c xs-security.json" and the default-env.json.
Now, the frontend endpoint "/user-api/currentUser" displays my new scopes properly.
But req.user._roles does not containt the new roles.
Question: Is there anything else to reconsider?
Thanks in advanced, H.Fischer.
Well, talking about documentation, you may have noticed that the _roles property is not really documented, right? It could have been spotted in req.user when it was possible to retrieve the entire object in older versions of the framework, but I guess it's there for internal usage (not direct access), so, maybe, that's why the product team have hidden the req.user properties when accessing it directly.
This might have been done to enforce developers to stick to whatever properties and methods are properly documented (in this case the req.user.is(<role>) method). Have you tried to use it to check whether it returns true for the roles you assigned to your user?
I have updated the post to reflect this situation.
BR,
Alessandro
OK, there is a post on answers.sap.com
https://answers.sap.com/questions/13672355/cap-authentication-is-it-role-or-scope.html
which says that req.user.is returns in fact true/false for scopes.
This is working correctly with my app.
Also 'req.user.attr.<Attrname>' returns true/false for attributes.
This also works.
My questions are answered. Thank you for your patience.