Skip to Content
Technical Articles
Author's profile photo DJ Adams

SAP Tech Bytes: Approuter User API Service

Use a simple example to kick the tyres of the new User API Service available in the @sap/approuter NPM package.

In the SAP Developer News episode for calendar week 04, there was an item covering updates to some NPM packages, including @sap/approuter – which now has a User API Service. If you’re interested in kicking the tyres of this new User API and getting a simple example up and running, this quick post is for you (there’s a link to files in our GitHub repo at the end).

The simplest thing that could possibly work is an @sap/approuter based app with a minimal configuration as described in the User API Service section. That app needs to be bound to a minimally configured instance of the Authorization & Trust Management service (aka “xsuaa”) and running in Cloud Foundry (CF).

App with minimal configuration

While the app is still effectively a Node.js package, there is no code, just configuration. First, in the form of the Node.js package.json file, which looks like this:

{
  "name": "userapitest",
  "scripts": {
    "start": "node node_modules/@sap/approuter/approuter.js"
  },
  "dependencies": {
    "@sap/approuter": "^9.1.0"
  }
}

The built-in approuter.js script is run directly when the app is started. It will look for configuration and find it in an xs-app.json file, the contents of which should look like this:

{
  "welcomeFile": "/user-api/attributes",
  "routes": [
    {
      "source": "^/user-api(.*)",
      "target": "$1",
      "service": "sap-approuter-userapi"
    }
  ]
}

Note that the welcomeFile property is used to automatically redirect us to one of the two endpoints supported by the User API Service – the “attributes” endpoint. The rest of the configuration is taken directly from the documentation.

The xsuaa instance needs some minimal configuration – an app name and the tenant mode in which it is to run. Here it is, in an xs-security.json file:

{
  "xsappname": "userapitest",
  "tenant-mode": "dedicated"
}

We can use a simple manifest for the CF command “cf push”, to save on typing. This defines what we need (although it doesn’t cause the creation of the xsuaa service instance, like an MTA based approach would, but we’re keeping things as simple as possible here). The contents of that manifest – in the manifest.yml file, are as follows:

applications:
- name: userapitest
  disk_quota: 256M
  instances: 1
  memory: 256M
  random-route: true
  services:
  - xsuaa-application
  stack: cflinuxfs3

Setting things up

So all we need to do now is to create the xsuaa service instance, with the “application” plan & those xs-security.json settings, specifying the name “xsuaa-application” (as that’s what is expected via the entry in the manifest), and then run the “cf push”.

Let’s take those steps now. First, the service instance creation:

; cf create-service xsuaa application xsuaa-application -c xs-security.json 
Creating service instance xsuaa-application in org xde3af75trial / space dev as sapdeveloper@example.com...
OK

Now the xsuaa service instance exists, we can create & deploy the app and have it bound to that service instance:

; cf push -f manifest.yml
Pushing from manifest to org xde3af75trial / space dev as sapdeveloper@example.com...
Using manifest file /Users/sapdeveloper/Projects/userapitest/manifest.yml
Getting app info...
Creating app with these attributes...
+ name:         userapitest
  path:         /Users/i347491/Projects/userapitest
+ disk quota:   256M
+ instances:    1
+ memory:       256M
+ stack:        cflinuxfs3
  services:
+   xsuaa-application
  routes:
+   userapitest-grouchy-chimpanzee-nx.cfapps.eu10.hana.ondemand.com
[...]
Waiting for app to start...
[...]
     state     since                  cpu    memory          disk         details
#0   running   2021-02-19T17:55:10Z   0.0%   35.6K of 256M   8K of 256M
;

And that’s it!

Checking the results

Accessing the route given in the output (the “userapitest-grouchy-chimpanzee.nz” route, here) in the browser results in this output, generated directly by the User API Service for the /user-api/attributes path:

{
  firstname: "SAP",
  lastname: "Developer",
  email: "sapdeveloper@example.com",
  name: "sapdeveloper@example.com",
}

And the output for the /user-api/currentUser path looks like this:

{
  firstname: "SAP",
  lastname: "Developer",
  email: "sapdeveloper@example.com",
  name: "sapdeveloper@example.com",
  displayName: "SAP Developer (sapdeveloper@example.com)",
}

Trying it yourself

There’s a branch for this SAP Tech Bytes post in the accompanying SAP Tech Bytes repository: Check it out and follow the simple instructions here:

https://github.com/SAP-samples/sap-tech-bytes/tree/2021-02-20-approuter-user-api-service

 


SAP Tech Bytes is an initiative to bring you bite-sized information on all manner of topics, in video and written format. Enjoy!

Assigned Tags

      12 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Holger Schäfer
      Holger Schäfer

      Hi DJ,

      thanks to point this quite helpful snipped, that even fits perfect in serverless manged approuter scenarios.

      Sadly, in my general usecase scenarios, i always need more on top of it like roles, sessionTimeout, etc. and therefore still need custom approuter middleware.

      Here is an example, if someone has the same needs:

      const approuter = require("@sap/approuter");
      const jwtDecode = require('jwt-decode');
      
      const ar = approuter();
      
      // add custom middleware
      ar.beforeRequestHandler
          .use('/me', function udinaMiddleware(req, res) {
              if (!req.user) {
                  res.statusCode = 403;
                  res.end('Missing JWT Token');
              } else {
                  const { user } = req
                  const token = jwtDecode(user.token.accessToken);
      
                  res.statusCode = 200;
                  res.setHeader("Content-type", "application/json");
                  res.end(JSON.stringify({
                      "loginName": user.id,
                      "firstName": token.given_name,
                      "lastName": token.family_name,
                      "email": token.email,
                      // missing additional properties so far!
                      "origin": token.origin,
                      "roles": token["xs.system.attributes"]["xs.rolecollections"],
                      "sessionTimeout": req.routerConfig.sessionTimeout
                  }));            
              }
          });
      
      // start the appouter
      ar.start();

       

      Following the SAP Graph Me API/Endpoint:

      https://explore.graph.sap/docs/beta/api/me

       

      sessionTimeout

      This helps to implement autoLogout inside your UI5 apps, see:

      https://blogs.sap.com/2019/08/12/sapui5-applications-with-approuter-sessions-and-automatic-logout/


      roles

      You will get an array of user role collection names to dynamically show/hide features that are maybe "Admin_Role" dependent.

      Best Regards

      Holger

      Author's profile photo DJ Adams
      DJ Adams
      Blog Post Author

      Thanks Holger!

      Author's profile photo Sumedh Agarwal
      Sumedh Agarwal

      Hi Holger and DJ,

       

      I have an UI5 app in cloud foundry where i want all the user details of the logged-in user. As of now i could get the basic details like name, and email. etc.

      But as part of further developments i want some additional as well including country, relationship to SAP etc.

      Can you guys help me with this. Please consider urgent.

      Thanks in advance!!

      Author's profile photo Holger Schäfer
      Holger Schäfer

      Hi Sumedh,
      concerning my solution ýou can only acces the info, that is part of the jwt (json web token).

      https://docs.microsoft.com/de-de/azure/active-directory/develop/id-tokens

      which is standartized against multiple IDPs.

      Concrete informations depends on your target. For Azure, you can maybe add additional user attributes and claims to the saml configuration to pass them while authentication, but finally (i think if understood corretly) you will need something more like a user info directory service accessable behind the approuter.

      Best Regards
      Holger

       

       

       

       

      Author's profile photo Sumedh Agarwal
      Sumedh Agarwal

      Holger,

       

      Thanks for replying back.

      But firstly, Can I check if the info I am looking for is part of jwt or not. if yes, then how?

       

      Secondly, as per my communication with IDS member they can only provide me in the SAML 2.0 config and not any service.

      If the info is received in the SAML 2.0 configuration then is there a way we can fetch it in our UI5 app?

       

      Your co-ordination is highly appreciated.

      Author's profile photo Janith Illangakoon
      Janith Illangakoon

      Hi,
      I'm using managed app router in my Fiori application.

      I've added the dependency in package.json and route in xsapp.json.

      My application is already bound to a xsuaa app.

      But when I run and check the user-api it gives a 404 error.

       

      When I check, 

      The URL which searches for the user-API is like this. 

      https://Mysubaccount.eu10.hana.ondemand.com/user-api/currentUser

       

      But when I use the URL of  index.html it works.

      https://Mysubaccount.eu10.hana.ondemand.com/c320938e-43g6-4rt4-86a3-2an6y4a61s7a.Myapp-1.0.0/user-api/currentUser

      What is the difference?

      Author's profile photo DJ Adams
      DJ Adams
      Blog Post Author

      Hi Janith, I'm not sure I understand exactly what you're asking, sorry. Can you provide more information, e.g. showing the context of the 404, and also how you're using the other URLs? Better still, please send this information in a question to the SAP BTP, Cloud Foundry environment tag where more folks will be able to assist and benefit. Thanks!

      Author's profile photo Janith Illangakoon
      Janith Illangakoon

      Thanks for the reply, once I deployed the application and run it from the HTML5 Application section it serach the API in following URL

      https://Mysubaccount.eu10.hana.ondemand.com/user-api/currentUser

      I'm using AJAX request to fetch the information from the controller.

       

      jQuery.ajax({
                      type: "GET",
                      contentType: "application/json",
                      url: "/user-api/currentUser",
                      dataType: "json",
                      async: false,
                      success: function(data, textStatus, jqXHR) {
      
                      alert(data);
                      },
                      error:function(e){
                          alert(e);
                      }

      But all other resources roots are stating from a URL like below

       

      https://Mysubaccount.eu10.hana.ondemand.com/c320938e-43g6-4rt4-86a3-2an6y4a61s7a.Myapp-1.0.0/

       

      And If I use the same URL for the User API, It works and return the user information

      https://Mysubaccount.eu10.hana.ondemand.com/c320938e-43g6-4rt4-86a3-2an6y4a61s7a.Myapp-1.0.0/user-api/currentUser

       

      Author's profile photo Ravindra Singh
      Ravindra Singh

      Hi Janith,

      were you able to fix this issue? I am also facing the same while calling user-api with Managed  approuter based UI5 App.

      Please let me know how you resolved this issue.

      Thanks in advance.

       

       

      Author's profile photo Jacob Tan
      Jacob Tan

      https://blogs.sap.com/2021/08/02/getting-user-information-in-btp-launchpad/

      check out the example in the blog above where there's a base url which you might be able to retrieve. hope it helps.

      Author's profile photo Ronald Rodriguez
      Ronald Rodriguez

      Do you have an example of how to implement this is a Fiori Freestyle App

      Author's profile photo DJ Adams
      DJ Adams
      Blog Post Author

      I'm afraid I'm not aware of an example like this - perhaps it would be worth checking in the Q&A area?