Technical Articles
How to get the email of the logged in user in Cloud Foundry
On SAP NEO we have a service you can call to get details about the logged in user. This is called the UserInfo service (How to get UserName and other login info in SAP UI5 application | SAP Blogs) This was reasonably well documented and worked well on the NEO platform but with the move to Cloud Foundry this no longer works so new techniques are required.
Part of this post is inspired by the sessions I co-wrote for TechEd18 and part by my colleague Sachit Aggarwal who thought it needed a more through treatment!
A bit of background.
I assume you are at least familiar with Cloud Foundry or you will not be here – you know what an Org, account, SubAccount and Space are.
When working within a space in cloud foundry, the user identity is managed by the User Authorization and Access service (UAA) or when using SAP services, the XSUAA. The service will apply the correct oAuth2 flows to enable you to get an oAuth token. However these flows are very complex to implement so SAP have provided another project to do this. This is called the AppRouter that will check to see the incoming request and if it is not authenticated and the request requires authentication then it will log you in to the application by managing the exchange between the browser and the XSUAA service.
Though there are lots of files that need to be created, you should be familiar with them if you have built a project for Cloud Foundry before.
Prerequisites
You need to have:
1. A valid SAP Cloud Platform account (Trial is sufficient) to be able to install and run the code.
2. The Cloud Foundry Command line tools installed. You can download it here
This exercise assumes that you have at least a basic understanding of SAP Cloud Platform and in particular Cloud Foundry. If not there are some great learning journeys on help.sap.com
Start by logging into your cloud foundry account using the command line tooling
cf login
You will now have been logged into an appropriate space in cloud foundry.
Create the xs-security.json file
First we need to create the xs-security file. This file is used to setup the default roles and scopes for the user.
{ "xsappname": "getuserinfo", "tenant-mode": "dedicated", "description": "Security profile of getuserinfo", "scopes": [ { "name": "uaa.user", "description": "UAA" } ], "role-templates": [ { "name": "Token_Exchange", "description": "UAA", "scope-references": [ "uaa.user" ] } ] }
Save the file and call it xs-security.json.
With this file created the instance of the security model can be created
cf create-service xsuaa application node-uaa -c xs-security.json
This completes the configuration of the service.
Create the xs-app.json file
The xs-app file is required to specify the routes for the app router. We could have put this into the approute-start.js file but I wanted to stick to best practices.
{ "welcomeFile": "index.html", "authenticationMethod": "route", "routes": [ { "source": "^/(.*)$", "localDir": "resources", "authenticationType": "xsuaa" } ] }
As you can see this just serves up files in the resources directory which is a standard behaviour for AppRouter. Now it is important to note the authenticationMethod property is set to route, so any accesses are checked to make sure the user is logged in and has a valid access token. If not they will be forced to login.
Also not the authenticatonType property that says this endpoint is protected by xsuaa.
Save the file.
This completes the configuration of the routing.
Create the manifest.yml file
The next file we need is the manifest file. The manifest file is used by the cloud foundry command line to do a build and deploy of the application. This file holds the name, resource requirements and type of application to build as well as the source file.
applications: - name: getuserinfoapp instances: 1 memory: 256M disk_quota: 1024M path: ./web buildpack: https://github.com/cloudfoundry/nodejs-buildpack services: - node-uaa
Note how the application tells cloud foundry it will consume the node-uaa service.
Save the file.
This completes the configuration of the deployment descriptor.
Create the approuter-start.js file
The most important feature of this post is the file that configures app router to support our new URL to obtain the email address of the logged in user.
const approuter = require('@sap/approuter'); var ar = approuter(); ar.beforeRequestHandler.use('/getuserinfo', function (req, res, next) { if (!req.user) { res.statusCode = 403; res.end(`Missing JWT Token`); } else { res.statusCode = 200; res.end(`My name is ${JSON.stringify(req.user.name, null, 2)}`); } }); ar.start();
Save the file.
The approuter extension is now complete.
Create the package.json file
Since this is a node.js project we need a package file to enumerate the dependencies
{ "name": "getuserinfo", "version": "0.0.1", "description": "Service to get user info", "dependencies": { "@sap/approuter": "5.8.0" }, "scripts": { "start": "node ./approuter-start.js" } }
This is very simple as we just have a dependency to the app router. Note however the start property in the scripts section. This property will, instead of launching the default app router will actually launch a javascript file that will start the app router. This file will allow us to create the app router with a custom configuration.
Save the file as package.json
The package description is now complete.
Create the index.html file
We create a simple HTML file to show the system is working. This is as basic an HTML file as possible
<!doctype html>
<html>
<body>
<div>
Welcome to user info
</div>
</body>
</html>
Save the file as index.html into the resource folder, creating the resources folder if it is not present.
The completes the configuration of the HTML file.
Putting it all together
Now all the files have been created, the application can be deployed to cloud foundry using the command line tooling.
The command we want to use is:
cf push
This takes the manifest.yml file and deploys the project as a node.js project. The package file contains a list of the dependencies as well as the start function to be called when the deployment is finished.
How it works
The browser will connect to the deployed application and check the user is logged in. If they are not then AppRouter implements the oAuth flow to log you in and get an oAuth token.
Additionally a JWT token will also be generated and this holds additional information about your session.
When a request arrives, the xssec (passport) library will check for the token and if not present execute the auth flow to get the JWT and oAuth token. If it is present it will parse the JWT and add the fields to the incoming request object to make it easier to consume the token in code.
Thoughtfully the app router annotates the req object with the relevant information by using the passport library together with the xssec library. A full list of node js packages can be found on help.sap.com
When the /getuserinfo path is called, the user info attached to the request (which should always be present as we have said all routes need a valid token) is read and the email address extracted and returned to the caller.
Wrapup
So folks, that is how you get the email address of the logged in user when in Cloud Foundry. More complex than NEO but more flexibility as well.
Awesome Paul. I was able to follow the tutorial and can get the email address of the logged in user. But the req.user object doesn't have information like firstname, lastname, user id etc. Anyway, we can get that info as well...
Hi,
Did you find any way out to get the information you required?
Hi Milton ,
May I know how you are able to get the email address . What url you are using to call the getUserinfo .
Hi Milton,
I'm not able to get the firstname, lastname and user id you mentioned. They are neither found in the req.user, or in the req.session.user.
Do you know where I can find them? 🙂
You can get logged user (first name, last name, email) information’s on Cloud Foundry out of the authentication service with reading JWT token. This works if standard SAP IdP provider is used. In Postman you can get this information calling API with GET operation https://<account>.authentication.eu10.hana.ondemand.com/userinfo
https://docs.cloudfoundry.org/api/uaa/version/74.20.0/index.html#user-info
Before making call you have to get Bearer token which can be retrieved by calling POST operation https://<account>.authentication.eu10.hana.ondemand.com/oauth/token
If you access from your application in CF where you are already logged in then you should use destination with following settings:
Name : xsuaa_api
Type: HTTP
URL : https://<account>.authentication.eu10.hana.ondemand.com/
Proxy type : Internet
Authentication : OAuth2UserTokenExchange
Client id : from XSUAA service instance
client secret : from XSUAA service instance
Token service URL : https://<account>.authentication.eu10.hana.ondemand.com/oauth/token
Destination can be consumed out of UI5 app with .ajax GET call or out of any other service. More on how to consume destination service :
https://help.sap.com/viewer/cca91383641e40ffbe03bdc78f00f681/Cloud/en-US/7e306250e08340f89d6c103e28840f30.html
Hi Robert,
We have recently moved to SAP cloud foundry. We have suite of apps which require the user api.
As you have mentioned, we can use the user api via destination. I followed the steps and tried to create a destination to utilize the user api service, but was unable to do so.
I created the xsuaa instance with all the available plans but each time the authentication method populated was different. I changed it to OAuth2UserTokenExchange and saved it, but it gave error. I went with the populated authentication method as well but again the result was same.
It would be great if you guide us on this, or provide the steps to achieve the same.
Regards,
Gaurav
Hi Robert,
I tried following the solution that you have suggested, but i am unable to get the logged in user info by calling the following API:
https://<account>.authentication.eu10.hana.ondemand.com/userinfo
I created an instance of XSUAA service, i called the following service in postman to get the access token:
https://<account>.authentication.eu10.hana.ondemand.com/oauth/token
When i call the "userinfo" API using the access token, i get the following error:
"The subdomain does not map to a valid identity zone"
Can you please advise how to resolve this.
Regards,
Arjun
Hi,
I added this code to an existing app on Web IDE.
But I'm also struggling with the URL.
Whatever I try (e.g. "...-approuter.cfapps.eu10.hana.ondemand.com/getuserinfo" or adding my app name and version) I receive a "Not found" error.
I have the impression the approuter-start.js is not executed. But I included
"scripts": {
"start": "node ./approuter-start.js"
}
in my package.json and the files are next to each other.
Has anyone an idea what's wrong or sample code or a hint how I could find out?
Hi Wolfgand,
I am also facing same issue.
Have you got working?
Hi Pravin
You managed to solve this issue, since I am trying to do this myself, but the same thing happens to me.