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: 
quovadis
Product and Topic Expert
Product and Topic Expert




















This brief is to describe how to leverage SAP BTP destination service to implement service to service calls from a SAP BTP hosted service (backend or frontend) against Microsoft Azure hosted resource (like MS Graph for instance), with or without a business user context.




Disclaimer:

  • The ideas presented in this blog are personal insights thus not necessarily endorsed by SAP.

  • Please note all the code snippets below are provided “as is”.

  • All the x509 certificates, bearer access and/or refresh tokens and the likes have been redacted.



  • Images/data in this blog post is from SAP internal sandbox, sample data, or demo systems. Any resemblance to real data is purely coincidental.

  • Access to online resources referenced in this blog may be subject to a contractual relationship with SAP and a S-user login may be required.



Putting it all together


In many ways this brief is a sequel to my other blog, namely AzureAD as an OpenID Connect (OIDC) and OAuth provider | SAP Blogs, where I showcased a user delegated authentication flow implemented with the OIDC Authorization code grant flow as a nodejs function with SAP BTP, Kyma runtime.

Let's see how to make service to service calls from SAP BTP into Microsoft Azure with either named or technical user context implemented with destinations.






Terminology and security concepts refresher:

  • On Microsoft AzureAD Identity Platform a web application is an OIDC service provider. Likewise, Microsoft AzureAD Identity Platform enterprise application is a SAML SSO service provider.  Azure platform workloads are defined as Application services.

  • On SAP BTP platform a BTP sub-account is a service provider (either OIDC or SAML). Trusted with either the default SAP ID or any other Identity Provider (IDP) of your choice. SAP BTP workloads typically require a runtime environment: either provided by SAP: Cloud Foundry, Kyma/Kubernetes or any Other runtime of your choice. With of a hundred business services that BTP platform has to offer, there are many of them available across multiple runtime environments.






Methodology explainer:

  • To pass a named user context we will be leveraging the OBO authentication flow with Azure Quovadis-Web application.

  • For a technical user context we will be leveraging the client credentials authentication flow that is performed on behalf of a technical user represented by the receiving application client_id. With this flow the client secret is a technical user password. However, as passwords may pose security risk, we shall be using client assertions in lieu.







  • OBO flow is much alike the user delegated authentication flow [cf, my previous blog], whereas client credentials flow is the application authentication flow. The following article may help understand the either flow.







  • In either case (OBO or client credentials) we shall be calling the Azure identity platform token issuance endpoint. The token issuance endpoint is protected with either a shared key or a client assertion of Quovadis-Web application



  • As aforementioned we shall be using OIDC client assertions in lieu of passwords. A client assertion is a JWT token signed with an x509 certificate known (uploaded) to the receiving Quovadis-Web application.



1. Service to service calls with a named user context


Microsoft identity platform OAuth 2.0 On-Behalf-Of flow , known as OBO,  allows exchanging a client application's id_token that has a named user context against a bearer access_token of a resource. More details on how to fetch this user JWT in appendix below,


OBO extends the urn:ietf:params:oauth:grant-type:jwt-bearer grant type and thus is very much alike the OAuth2JWTBearer authentication flow offered by the destination service.

The destination service OAuth2JWTBearer authentication flow requires passing the user's JWT token in the X-user-token header of the Find Destination call..

However, the OBO flow accepts the user JWT token (a user's assertion) as a property value instead, what eliminates the requirement of passing it in the X-user-token header of the Find Destination call. Should you prefer the X-user-token header method this works the either way.

Here goes an id_token that has the user context (email claim) of a client application. The id_token audience (aud claim below) - is the client id of the OIDC application.
{
"typ": "JWT",
"alg": "RS256",
"kid": "<kid>"
}.{
"aud": "5a945db3-f165-4b7b-abbd-xxxxxxxxxxxx",
"iss": "https://login.microsoftonline.com/<tenant_id>/v2.0",
"iat": 1663452399,
"nbf": 1663452399,
"exp": 1663456299,
"aio": "<aio>",
"email": "foo.bar@microsoft.com",
"idp": "https://sts.windows.net/42f7676c-f455-423c-82f6-xxxxxxxxxxxx/",
"rh": "<rh>",
"sub": "<sub>",
"tid": "<tenant_id>",
"uti": "<uti>",
"ver": "2.0"
}.[Signature]

 

Quovadis-AzureAD-JWT-ppm - the On-behalf-of destination definition:


{
"owner": {
"SubaccountId": "<SubaccountId>",
"InstanceId": null
},
"destinationConfiguration": {
"Name": "Quovadis-AzureAD-JWT-ppm",
"Type": "HTTP",
"URL": "https://graph.microsoft.com/v1.0/me:443",
"Authentication": "OAuth2JWTBearer",
"ProxyType": "Internet",
"tokenServiceURLType": "Dedicated",
"tokenService.KeyStorePassword": "<KeyStorePassword>",
"clientId": "5a945db3-f165-4b7b-abbd-xxxxxxxxxxxx",
"Description": "https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow",
"tokenService.body.client_assertion": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsIng1dCI6IkRRTU9kcjlJVFp4aGQ2TllJN2w4eTZMSWRUdz0ifQ.eyJpc3MiO(truncated)H7XLf1EgHuWHw9Y5cGekyS8A_ZPIVM",
"scope": "openid email offline_access https://graph.microsoft.com/.default",
"x_user_token.jwks_uri": "https://login.microsoftonline.com/<azureAD_teant_id>/discovery/v2.0/keys",
"tokenService.body.assertion": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjJaUXBKM1VwYmpBWVhZR2FYRUpsOGxWMFRPSSJ9.eyJhdWQiOiI(truncated)th7ougSpgvkKaAxA6lOB60PVQEj7LAdqaUqJ8MN36rBXYUJnaN3sgJbrlbQDjNE2FP9sw",
"tokenServiceURL": "https://login.microsoftonline.com/958a12e6-de37-4185-8ea2-c3f59ed0f97f/oauth2/v2.0/token",
"tokenService.KeyStoreLocation": "<KeyStoreLocation>",
"tokenService.body.requested_token_use": "on_behalf_of",
"tokenService.body.client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
},
"authTokens": [
{
"type": "Bearer",
"value": "eyJ0eXAiOiJKV1QiLCJub25jZSI6IkJubGc1eWJMM29RS0xtcnAwLWVnLTN3dVZHQktlVUNIUGRvSE9FTDVpaWsiLCJhbGci(truncated)yfAU2hXe3xNU2mo7GGjIHpOZugwacgoRQ040RuTf8wBr-aHpANUXPvx6-xp05dYSkvf6XDzjOAd9cJg0A4R6PLFGVSOnQ",
"http_header": {
"key": "Authorization",
"value": "Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI6IkJubGc1eWJMM29RS0xtcnAwLWVnLTN3dVZHQktlVUNIUGRvSE9FTDVpaWsiLCJhbGciO(truncated)yfAU2hXe3xNU2mo7GGjIHpOZugwacgoRQ040RuTf8wBr-aHpANUXPvx6-xp05dYSkvf6XDzjOAd9cJg0A4R6PLFGVSOnQ"
},
"expires_in": "1613",
"scope": "email openid profile https://graph.microsoft.com/Calendars.Read https://graph.microsoft.com/Calendars.Read.Shared https://graph.microsoft.com/Calendars.ReadWrite https://graph.microsoft.com/Calendars.ReadWrite.Shared https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/Mail.Read.Shared https://graph.microsoft.com/Mail.ReadBasic https://graph.microsoft.com/Mail.ReadWrite https://graph.microsoft.com/Mail.ReadWrite.Shared https://graph.microsoft.com/Mail.Send https://graph.microsoft.com/Mail.Send.Shared https://graph.microsoft.com/Tasks.Read https://graph.microsoft.com/Tasks.Read.Shared https://graph.microsoft.com/Tasks.ReadWrite https://graph.microsoft.com/Tasks.ReadWrite.Shared https://graph.microsoft.com/User.Read https://graph.microsoft.com/.default",
"refresh_token": "0.AVkA5hKKlTfehUGOosP1ntD5f7NdlFpl8XtLq73n19NtGyNZAJo.AgABAAEAAAD--DLA3VO7QrddgJg7WevrAgDs_wQA9P9qaIHJUpAw6SAIk02ESRo4DTgdzWFRBEbqAeS1BjSyhN5UYYXUtbXs7rU-PmqJQDKv8Ovq_T4Qvl_gmPNia6R2-wPwthKp4TWx6SnHoaBcSrqGjgO0CGIsYZs_Jb2QA-joU93r4mxa83bT8WRrweS53FQGrS2ACCa_FazILmSRSz6MtTbMEycELsG_ou1U7VldFssSNXKWpaY1k9CWWGEunLVmODHujbjpT9FD1WH7f__sjj2WVtle19WvxoxJngsuKU0Oj-mj-a2NJw_mMweQ14nW7S9E93OE6kQWRDuk3mPqLLq29PUp-0-ogU1xfIV26myC_EjZ16DWbqyytQZdPG77iZjtl0_v-nEz3iAff0JFE34NyBhMJlTCwQRkFH0qx12mGXJDs7iPITuxA6X_CLkyug22FY_7RPRQgrBjwS_4PkXppu85HZJoCCp_cnOHzICS6D621gTCyFz0vUQy8AyE9vdcTybkkI2MnXnavHKsEeVZ-OuAfCyfPkgHOd2LbHm73KCd0mFDpnQMfK1rOMnoaVa5dQudOtjWl2KZAvK6d_MyGxEN-R8O-Ac2-XT3i2PQ4oK0iJb0S0qNmBP6LuOwwtDiCFUgyZ1-LjFCZzyMQ0W92DqO0OPIZKdjywyWao-Tu7vynS6Rbs7NOmryx03_kheAcJqahL9aZg7VTqQG_YEF0kaw7GxHDORmNo3-5fw007xbXBQnjxIyUFd93xiIWbaMwneiojXg8CFEOvHbuMfMqy9AdTLyStvpx_3WKVSGQQT7-nOoov3BzoyO7pNmrGUI7Dw6iRtiTgc5STvPlw32wNo1gFhveEJ4qX8enW7xVEYBsFL7tDraKmM21eA"
}
]
}

Here goes the exchanged access bearer token. It has a user context so can be used with both the technical endpoints of a resource and the ones that require passing of the user context (like /me).
{
"typ": "JWT",
"nonce": "<nonce>",
"alg": "RS256",
"x5t": "<x5t>",
"kid": "<kid>"
}.{
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/<tenant_id>/",
"iat": 1663620945,
"nbf": 1663620945,
"exp": 1663624653,
"acct": 0,
"acr": "0",
"aio": "<aio>",
"altsecid": "5::10037FFE94410318",
"amr": [
"rsa",
"mfa"
],
"app_displayname": "Quovadis-SAP",
"appid": "5a945db3-f165-4b7b-abbd-xxxxxxxxxxxx",
"appidacr": "2",
"email": "foo.bar@microsoft.com",
"family_name": "Bar",
"given_name": "Foo",
"idp": "https://sts.windows.net/42f7676c-f455-423c-82f6-xxxxxxxxxxxx/",
"idtyp": "user",
"ipaddr": "<ipaddr>",
"name": "LUC-ISVENG",
"oid": "<oid>",
"platf": "5",
"puid": "<puid>",
"rh": "<rh>",
"scp": "Calendars.Read Calendars.Read.Shared Calendars.ReadWrite Calendars.ReadWrite.Shared email Mail.Read Mail.Read.Shared Mail.ReadBasic Mail.ReadWrite Mail.ReadWrite.Shared Mail.Send Mail.Send.Shared openid profile Tasks.Read Tasks.Read.Shared Tasks.ReadWrite Tasks.ReadWrite.Shared User.Read",
"sub": "<sub>",
"tenant_region_scope": "NA",
"tid": "<tenant_id>",
"unique_name": "foo.bar@microsoft.com",
"uti": "<uti>",
"ver": "1.0",
"wids": [
"62e90394-69f5-4237-9190-012177145e10",
"b79fbf4d-3ef9-4689-8143-76b194e85509"
],
"xms_st": {
"sub": "<sub>"
},
"xms_tcdt": <xms_tcdt>
}.[Signature]

Eventually, we can call the MS Graph's resource endpoint with the access token:
curl https://graph.microsoft.com/v1.0/me -H "Authorization: Bearer <access_token>"

{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
"businessPhones": [],
"displayName": "LUC-ISVENG",
"givenName": "Foo",
"jobTitle": null,
"mail": null,
"mobilePhone": null,
"officeLocation": null,
"preferredLanguage": null,
"surname": "Bar",
"userPrincipalName": "foo.bar_sap.com#EXT#@ppmxxxxxxx.onmicrosoft.com",
"id": "<id>"
}

2. Service to service calls with technical user context


Microsoft offers ample instructions covering this use case. However, in our case, the calling application/workload is hosted on one of SAP BTP runtimes whereas both the receiving OIDC application and the backend resource are, respectively, registered with and hosted on MS Azure.

2.1. Shared secret option.


Let's start with the shared secret option described here

We are going to request a bearer access token for the receiving Quovadis-Web application client_id (=a technical user name).

This can be handled using an OAuth2ClientCredentials destination out-of-the-box as follows:
{
"owner": {
"SubaccountId": "<SubaccountId>",
"InstanceId": null
},
"destinationConfiguration": {
"Name": "Quovadis-AzureAD-clientSecret",
"Type": "HTTP",
"URL": "https://graph.microsoft.com/v1.0/$metadata:443",
"Authentication": "OAuth2ClientCredentials",
"ProxyType": "Internet",
"tokenServiceURLType": "Dedicated",
"tokenService.KeyStorePassword": "<KeyStorePassword for mTLS communication>",
"clientId": "8e4ed817-c1b0-4cab-89a0-3bbfa1234567",
"Description": "OAuth2ClientCredentials",
"scope": "openid email offline_access https://graph.microsoft.com/.default",
"clientSecret": "c91z***********************",
"tokenServiceURL": "https://login.microsoftonline.com/3e2cfdd6-0dc4-4387-a7cf-xxxxxxxxxxxx/oauth2/v2.0/token",
"tokenService.KeyStoreLocation": "<KeyStoreLocation for mTLS communication>"
},
"authTokens": [
{
"type": "Bearer",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dC(truncated)YZ_SDLqvwl0cojOsmek_cJAfw_EqdDysfbr8XGXcUKKQpt-uQ",
"http_header": {
"key": "Authorization",
"value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dC(truncated)YZ_SDLqvwl0cojOsmek_cJAfw_EqdDysfbr8XGXcUKKQpt-uQ"
},
"expires_in": "3599"
}
]
}

Good to know:

  • The tokenService.KeyStorePassword and tokenService.KeyStoreLocation properties are for the mutual TLS communication against the OAuth server. However, if mTLS is not required or not enabled, you may remove these properties from the definition above.


2.2. Client assertion (certificate) option.


The certificate or client_assertion method to request a bearer access token described here is more complex to implement.

But it has one undeniable advantage: it mitigates the security risk posed by using passwords. And, furthermore, certificates can be rotated, thus providing additional security.

(Just an observation, the client_assertion option on Azure is quite similar to the x509 option offered for instance by the xsuaa service on BTP as they both eliminate the use of secrets when calling the token endpoint.)

At the time of this writing, the destination service does not offer a built-in support to handle client assertions. However, it has a built-in mechanism to let you pass additional parameters when calling the remote OAuth server token endpoint: either with additional headers, url params (queries) or as data (body).

I used the latter option as depicted below:
{
"owner": {
"SubaccountId": "<SubaccountId>",
"InstanceId": null
},
"destinationConfiguration": {
"Name": "Quovadis-AzureAD-JWT",
"Type": "HTTP",
"URL": "https://graph.microsoft.com/v1.0/$metadata:443",
"Authentication": "OAuth2ClientCredentials",
"ProxyType": "Internet",
"tokenServiceURLType": "Dedicated",
"clientId": "8e4ed817-c1b0-4cab-89a0-3bbfaxxxxxxx",
"Description": "OAuth2ClientCredentials",
"tokenService.body.client_assertion": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsIng1dCI6IkRRTU9kcjlJVFp4aGQ2TllJN2w4eTZMSWRUdz0ifQ.eyJpc3MiO(truncated)WjmL07s_ucHfblfVApYfp1m3eeq-_G35MfUCT_HFL-bVMFV6oq9RWuO4NwtCPT_VA23Qgaf6P8HXBwTcQ9aidKl7hJjIgK93t-9zOyASf0ML44KfQMP67j5jVdIY",
"scope": "openid email offline_access https://graph.microsoft.com/.default",
"tokenServiceURL": "https://login.microsoftonline.com/3e2cfdd6-0dc4-4387-a7cf-xxxxxxxxxx/oauth2/v2.0/token",
"tokenService.body.client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
},
"authTokens": [
{
"type": "Bearer",
"value": "eyJ0eXAiOiJKV1QiLCJub25jZSI6Ik9IOUdRVk(truncated)xM6d1lwgMpg9VlpjfY2BtO1Nxp9rYfmgsl77QFcYnBNfj40zWQEGW8QGCk_EzzNXRTUIA"",
"http_header": {
"key": "Authorization",
"value": "Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI6Ik9IOUdRVk(truncated)xM6d1lwgMpg9VlpjfY2BtO1Nxp9rYfmgsl77QFcYnBNfj40zWQEGW8QGCk_EzzNXRTUIA"
},
"expires_in": "3599"
}
]
}

When looking at the above definition the only additional parameter that requires some more insight is the value of tokenService.body.client_assertion. More details in the appendix here.

You may also notice the clientSecret is no more...

Last but not least. Here goes a decoded access_token. Please note the audience claim identifies the resource.
{
"typ": "JWT",
"nonce": "<nonce>",
"alg": "RS256",
"x5t": "<x5t>",
"kid": "<kid>"
}.{
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/<azureAD_tenant_id>/",
"iat": 1663621969,
"nbf": 1663621969,
"exp": 1663625869,
"aio": "<aio>",
"app_displayname": "QuoVadis-Web",
"appid": "8e4ed817-c1b0-4cab-89a0-3bbf1234567",
"appidacr": "2",
"idp": "https://sts.windows.net/<azureAD_tenant_id>/",
"idtyp": "app",
"oid": "<oid>",
"rh": "<rh>",
"sub": "<sub>",
"tenant_region_scope": "EU",
"tid": "<azureAD_tenant_id>",
"uti": "<uti>",
"ver": "1.0",
"wids": [
"<wids>"
],
"xms_tcdt": <xms_tcdt>
}.[Signature]

Last but not least, here goes a call against the $metadata endpoint of our resource - the MS Graph service.
curl https://graph.microsoft.com/v1.0/$metadata -H "Authorization: Bearer <access_token>"

{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata",
"value": [
{
"name": "invitations",
"kind": "EntitySet",
"url": "invitations"
},
{
"name": "users",
"kind": "EntitySet",
"url": "users"
},
{
"name": "applicationTemplates",
"kind": "EntitySet",
"url": "applicationTemplates"
},
]
}

Caveats


Until the client assertion option is natively supported by a destination service the client assertion must be (re)generated outside of a destination definition and then the destination definition must be amended with its value. Only then a call to Find Destination can follow.

Albeit not that practical for approuter destinations, it still might work well in cases the client assertion does not need to be refreshed every time the destination is called. More details in the appendix here.

Conclusion


There is no mandate to use the destination service at all times. Eventually, both user delegated and application flows can be easily implemented with just a few lines of code, without it.

So why bothering that much about getting that done with destinations and the destination service?

Well, the sweet spot of destinations is that they make really easy to define business and/or functional logic (=routes) and that the approuter (either SAP managed or a standalone one) can prropagate them accordingly.

That approach greatly simplifies the design, integration and maintenance of modular applications with multiple functional endpoints.

Last but not least. I hope you have enjoyed reading this post. As usual, please make use of the comments section below in case of questions and to provide feedback.





Appendix


Propagate a named user context


The most straightforward option to propagate a user context and fetch the user's id_token is to use the OAuth 2.0 code grant flow described here: Authorize access to Azure Active Directory web applications using the OAuth 2.0 code grant flow | Mi... and here: Microsoft identity platform code samples | Microsoft Docs

Or you may goto my sibling blog, namely AzureAD as an OpenID Connect (OIDC) and OAuth provider | SAP Blogs to see how it can be done in nodejs as a kyma function.

Generate a client assertion


Assuming you have uploaded a valid x509 certificate (which, for the record, can be self-signed) to your Azure hosted application the first thing you need to do is to generate a client assertion.








QuoVadis-Web | Certificates & secrets

 


A client assertion is a JWT token signed with the private key of your x509 certificate uploaded to the Quovadis-Web application on Azure side. (You may have uploaded several x509 certificates and thus can generate several client assertions...)

This article, Microsoft identity platform application authentication certificate credentials | Microsoft Docs, describes the mandatory format of this JWT token.

In order to generate a JWT you will need a flattened private key and the fingerprint (thumbprint) of the public x509 certificate.

For educational purposes: the below code snippet shows how this could be done.

Alternatively, quoting after Microsoft documentation:
To compute the assertion, you can use one of the many JWT libraries in the language of your choice - MSAL supports this using .WithCertificate(). The information is carried by the token in its Header, Claims, and Signature.

const jwt = require('jsonwebtoken');
const { randomUUID } = require('crypto');

const key = '-----BEGIN PRIVATE KEY-----\nMIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDFz/eQv30tj5oC\nLjT1Im7OtVAVo6mB/wQbEpbOh3LSI8h/f00fwLMJ/uQ3nYHiwqsElTvKA0h0B5tm\n79w(truncaated)LSxBTGdiZznqgLKnImxU1WDSA2xlKJy7J\nAwx8lLYgANSJ7qkKPgPR/t5ZHrx/plY=\n-----END PRIVATE KEY-----\n';

// https://www.samltool.com/fingerprint.php
// the fingerprint can be fetched from the OIDC application manifest
const fingerprint = '0d030e76bf484d9c6177a35823b97ccba1234567';

// https://stackoverflow.com/a/56236665
//
function hex2bin(hexSource) {
var bin = '';
for (var i=0;i<hexSource.length;i=i+2) {
bin += String.fromCharCode(hexdec(hexSource.substr(i,2)));
}
return bin;
}
function hexdec(hexString) {
hexString = (hexString + '').replace(/[^a-f0-9]/gi, '')
return parseInt(hexString, 16)
}
const x5t = hex2bin(fingerprint);

function generateAccessToken_azure(client_id, token_endpoint) {

console.log('generateAccessToken_azure: ', randomUUID());
return jwt.sign( {iss: client_id, sub: client_id, aud: token_endpoint, jti: randomUUID() }, key, { algorithm: 'RS256', header: {x5t : btoa(x5t) }, expiresIn: '1d' } );
}

Please note: the client assertion below has the Azure Identity token issuance service endpoint as its audience and the OIDC application client_id as the issuer.






jwt.ms


client assertion (=JWT) decoded



Certificate fingerprints (thumbprints)


A certificate fingerprint, or a SHA-1 thumbprint of a X.509 certificate. is a hexadecimal number (and not a string). It needs to be converted to a binary format before being Base64-encoded as a x5t JWT header claim.

Failure to do so will result in an invalid JWT header and errors for instance: AADSTS700027: Client assertion failed signature validation | Microsoft Community

Good to know:

  • The uploaded certificates thumbprints can be copied directly from the manifest of the Quovadis-Web application.

  • Alternatively, the following tool is of help when dealing with a public x509 certificate especially when it comes to thumbprints: https://www.samltool.com/fingerprint.php

  • SAP BTP,Kyma runtime function could be used to implement the JWT token generation logic and expose it as an endpoint via a protected API rule.


1 Comment