Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
Alex_Hauck
Product and Topic Expert
Product and Topic Expert
2,065

Intro and Motivation


The idea of writing a blog post about how to receive a user-specific JSON-Web-Token using the One-time Passcode flow with a custom SAML Identity Provider came up just before the cozy Christmas season as I was preparing the development set-up for an upcoming customer project in 2021.

Before setting the stage for this post, let me put a disclaimer first:

  • If you are totally new to terms like JWT, Authorization and Authentication Management (in short XSUAA) on Cloud Foundry runtime, Role Collections and many more, I recommend reading the blog post of my colleague jeffrey.groneberg first, as it explains all those basic terms and concepts. Without having a basic understanding of those topics you might have a tough time reading and understanding this advanced post as it builds up on this knowledge.

  • Moreover I especially want to thank two great colleagues. Without himmelsbachw and nicolai.benz this post wouldn't have been possible. THANK YOU both for your ideas, contribution and great support!


Having said this, lets jump into the requirements and scenario of this post:

You now for sure want to ask yourself: Why the heck does one need a user-specific JWT? Well for the upcoming customer project I wanted to set-up a Role-Collection mapping based on assigned user groups. To validate the correct scope assignment carried by the JWT, I wanted to decode the issued JWT and check whether all intended scopes are part of it and can be used for further authorization checks.

The infrastructure and solution architecture of the mentioned project is for itself not really specific, I even would call it mainstream. The requirements of this can be described as follows:

  1. "As a developer using SAP Cloud Platform I do not want to make use of the default SAP ID service, but rather want to reuse user accounts and their information stored and maintained on my custom Identity Provider (IdP)".

  2. "As a developer and/or security expert I want to use the Identity Authentication Service running on SAP Cloud platform and establish trust between my Identity Authentication tenant and a dedicated subaccount on SAP Cloud Platform".

  3. "All subsequent authentications should be enriched by authorization information based on the respective and already mentioned group mapping, so that I as a developer can make sure to only expose information based on assigned role collections with its associated scopes and attributes".


So far I am sure you as a reader of this post would most likely agree, those high-level requirements can be verbalized for a lot of development (and extension) scenarios within the SAP context. If you have worked already with the mentioned components, the following high-level architectural model should not be new to you. It shows the established trust between the custom Identity Authentication tenant with a dedicated subaccount of SAP Cloud Platform. Moreover it represents the usage of the Approuter as entry point for incoming requests and the necessary service binding with a XSUAA instance to issue a valid JWT during runtime.


By default the Approuter injects the JWT into the incoming request made by the End user. This flow itself out-of-the-box does not return the JWT in a way I could check about the information carried by the token. However this scenario serves a the frame for a common understanding of the domain we are moving in.

Although for sure one could manipulate the Approuter returning the JWT, but my aim is to fetch the token from the authentication url being assigned to the XSUAA instance. For testing purposes it might be ok to implement kind of a request handler printing out the JWT as part of the  which allows me to decode it and read all carried information. Lets see how one can achieve this.

Password Grant will do the Trick, right?!


However the part of using a custom IdP and not making use of the default SAP ID service unpredictably came up with one challenge for me which I'd like to summarize as follows:

The JWT makes sure to transport and exchange authorization relevant information between components being used by your application. To verify whether the assignment, based on requirement number 3 from above did work correctly, I intended to receive a user-specific JWT for a dedicated (business) user. To receive such a token by using a REST-Client, I came across several blog posts, but also some tutorials which explain how to achieve this by using the Password Grant flow.

Investigating further about the needed parameters to receive a user-specific JWT by using this kind of flow, I naively thought: "Well, thats exactly what I need. Simply provide the username and password of the context-given user plus some general available attributes (e.g. clientid, clientsecret, grant_type and token_format) and receive the JWT which carries all associated scopes and attributes...

So far so good, I put all the values into Postman and was looking forward to initiate the request and following this receiving the JWT. However, what can I say: It did not work. The only thing I got back from the authentication authority was the following:
{  
"error": "unauthorized"
"error_description": "Bad credentials"
}

Ok I thought, may be wrong password for the user. So lets try it again... but no, again same response. Next, lets ask Google (who else?!) and quite soon I cam across a well described SAP Note stating the following:

"The grant type "password" is supported for OpenID Connect (OIDC) Identity Providers, not for SAML Identity Providers. By default, SAP ID Service (origin=sap.ids) is used, so it will work with SAP users (e.g. S-users). SAP Cloud Platform Identity Authentication Service (IAS) can be configured as an OIDC IdP as well (origin=sap.custom). Third-party identity providers are not supported."

Ok, as requested by the formulated requirements from above - we have a custom Identity Authentication tenant connected which is not the default one (SAP ID). So this behaviour is well known and it's not me messing it up. However, if its not possible using the Password Grant flow - how do I achieve my goal fetching a user-specific JWT to verify the correct scope/attribute assignment??

One-Time Passcode to the Rescue


Why not taking a look into the official Cloud-Foundry documentation explaining all the available token flows. Beside the already mentioned Password Grant Flow, there are two more possibilities receiving a JWT. First, the Authorization Code Grant Flow and second the One-Time Passcode flow. The first alternative already got explained within this blog post which means this post will focus on the One-Time Passcode flow.

If you want to learn how to interact and which parameters are needed to initiate the one or another flow, the official Cloud Foundry documentation is a great starting point. Let me put the following excerpt taken from this documentation into this post. This should help to explain how to fetch a user-specific token using the One-Time Passcode flow to fetch the JWT:


As marked in red, those values shown above were the most interesting ones for me. As input parameter I just had to put the grant_type (here: password). Moreover for this flow a passcode is required and I'll come to that quite soon. The token format as an optional parameter can also be provided. As I am interested in a user-context-specific one containing the user information plus the assigned scopes and attributed, I'll provide this attribute paired with the value "JWT".

How to get the Passcode


Before issuing the request, let me tell you how I received the passcode.

According to the documentation the passcode is defined as the following:

"[...]the one-time passcode for the user which can be retrieved by going to /passcode[...]".

This value can be retrieved by navigating to your Authentication URL and adding the "/passcode" suffix to it. This URL can be fetched from your XSUAA instance (Service Key Attribute "URL"). You will be prompt to sign in with the user in which context you want to receive the JWT. Having entered your username + password and being authenticated successfully, you will be forwarded to a page providing the "Temporary Authentication Code". This page looks like the following:


Copy and keep this Passcode for the next steps to come 😉...

Fetch the JWT by using CURL


With the last step, the JWT can be received. Either by using Postman or a terminal making use of a CURL request. I will use the latter to demonstrate how to fetch this token:

Based on the documentation, the CURL request needs to be initiated with the following parameters:
curl 'https://<authentication-url>/oauth/token'
-i -u '<client-id>:<client-secret>' -X POST \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-d 'grant_type=password&passcode=<Temporary Authentication Code>&token_format=jwt'

Small hints how to receive the values described above:

  • authentication-url: Service Key Attribute "URL" from XSUAA instance

  • client-id: Service Key Attribute "clientid" from XSUAA instance

  • client-secret: Service Key Attribute "clientsecret" from XSUAA instance

  • Temporary Authentication Code: Received Code from step "How to get the Passcode"


The clientid and clientsecret serve as user and password as the CURL request makes use of a basic authentication. This will be set by using the parameters -i and -u in the snippet above. Please note also, the Temporary Authentication Code is only valid for ONE request. Afterwards it is invalidated and cannot be used anymore to fetch a JWT.

The result of receiving a token looks like the following, whereas the marked area in blue represents the encoded and due to security reasons blurred JWT:


 

Decoding the JWT and validating the carried Information


Now as the previous section has shown how to receive the encoded token, the next step is to decode it and the validate the assigned scopes etc. For this I installed a nice helper, in fact a node package which is called "jwt-cli". With the help of this tool it is quite easy to decode the JWT directly inside the terminal. As we already have fetched the token using CURL from within the terminal I do not want to leave it and decode the token using this node package right inside the terminal. If you want to do so as well, feel free to install it via npm. Here you can find the corresponding GitHub repository from which I took the respective command to install it as a global available dependency:
npm install -g jwt-cli

Afterwards check whether the installation has been successfully by typing "jwt" inside a terminal console. The result should look as follows:


Next, let's finally decode the token. To achieve this, mark the token from above and copy it. Afterwards type the following to pipe the JWT to the previously installed node dependency "jwt":
pbpaste | jwt

As a result, you receive the fully decoded JWT with all its associated information. When examining the decoded token, you'll also find the section "scope" which contains an array of all assigned scopes to this user-specific token. The snippet below illustrates this:


The explained way of decoding the JWT is only one way to achieve this. Of course you can also make use of jwt.io as already describe by other blog posts.

Conclusion


With this post I gave you some insights on how to fetch a JWT by making use of the One-time Passcode flow. As it was not possible to make use of the Password Grant flow fetching a token for validation purposes the described flow can serve as an alternative. The following diagram gives a consolidated overview of the necessary steps described above to receive the JWT.


I hope with this post I could show you one additional alternative on how to receive a JWT by using CURL or any other REST-client of your choice.

For questions or remarks just leave a comment below this post or feel free to reach out to me via mail.. 😉
1 Comment