How grant-types keep your application secure – Exercise 3

This post has the third set of exercises demonstrating different grant types described in my article “How grant-types keep your application secure?“. This exercise looks at the grant types that propagate users from outside of the identity zone of SAP Authorization and Trust Management service. The lab exercises are for examining the influence of configuration parameter grant-type. You may use this as a setup for further exploration. I would be curious to know what you explored. Please share your own experiments in the comments.

Pre-requisite:

You have completed the lab setup as outlined in the post.

There are additional requirements and setup needs for individual exercies. These are listed in the corresponding sections that detail the steps of the exercise.

Introduction

I found httpyac to be a great tool for experiments with oAuth. The file .env you created during lab setup is for configuring the tool httpYac. This is to avoid typing the configuration every time. We will use this tool extensively in the exercises.

In this exercise we will examine the following grant types:

  • JWT Bearer Token grant
  • SAML2 Bearer grant

In this exercise we explore the options for an external application to request services of Business Logic Applications in SAP BTP.

Here, an external application is the one which has authorizations from a user in a context outside of the XSUAA service trusted by the Business Logic Application. In this case, it is not able to use the methods from the previous exercise for user propagation. An external application in this case could be one deployed in another region of SAP BTP or an application not based on SAP BTP. Mutual trust in this case, has to be established via a trusted Identity Provider.

Side Note:

There are a few methods for trusting and accepting tokens from clients registered with identity zones/subaccounts in the same SAP BTP region. There is no need for additional steps to establish trust as they are all based on the same trusted domain. These methods provide means for a client to add another client’s scopes to the tokens it receive from XSUAA service. This results in adding the target client to audience of the tokens they receive from XSUAA service. So these ae accepted by the library validating the tokens. In the case of multitenant scenario, the same client has authorization to request tokens from other subdomains, those which have subscribed to the application. The tokens issued for these clients from other subdomains also will have this shared client in the audience and the shared scopes as scopes. So these are naturally accepted by the library. Net result is that the tokens issued for foreign subdomains or other clients are accepted by the Business Logic Application when using the standard libraries from SAP BTP for validating tokens. The methods I refer to here are:

  • Explicit grants from the application to other clients/applications.
  • Subscription to multitenant applications using SAP SaaS Provisioning Service
  • Open Service Broker based on broker plan of xsuaa service

We will side-step a discussion of these. For the purpose of the exercises below, applications in other subaccounts of the same region are also external applications. They have not “established trust” using any of the methods listed above.

Additionally, @sap/xssec can be configured to accept tokens from multiple clients since version 3.1.0.

JWT Bearer Token

We looked at JWT Bearer Token grant in the last exercise. An id token issued for a client was used to request an access token for another client. But both these clients were from the same subaccount. JWT Bearer Token grant can be used to request access token using an id token issued by the trusted Open ID Connect based identity provider too. This is what we will examine in this exercise.

Pre-requisite:

Only an SAP Cloud Identity Services tenant can be configured as Open ID Connect based identity provider. SAP BTP trial does not offer SAP Cloud Identity Services tenant. This environment will not be sufficient for this exercise.

You should have a Custom Application Identity Provider with protocol OpenID Connect listed in Trust Configuration. Perform the steps outlined in Establish Trust and Federation Between UAA and Identity Authentication to have this. Refer to this blog by Kumar Amar for performing this setup using API.

The Custom Application Identity Provider is set as Available for User Logon.

The expected state is as illustrated in the picture below (picture from the blog by Kumar Amar.

You have credentials of a user configured in the Custom Application Identity Provider with which you can login.

The subaccount has entitlement for application plan of service “Cloud Identity Services” (service name: identity).

We need to make changes to the deployment, and enable the grant type JWT Bearer Token for the xsuaa service instance for Business Logic Application. So, deploy the mta again, with an extension descriptor. This descriptor configures the xsuaa service instances with the required grant type.

Copy the contents below as file mta-ext-ex3a.yaml

---
_schema-version: '3.1'
ID: cf-application-ext-3a
extends: cf-application
version: 1.0.0 resources: - name: cf-application-uaa parameters: config: oauth2-configuration: grant-types: - urn:ietf:params:oauth:grant-type:jwt-bearer - password - name: cf-approuter-uaa disabled: true parameters: config: oauth2-configuration: grant-types: []

Execute the following commands to deploy the package with the extension descriptor copied in the earlier step. The extension descriptor, adds jwt-bearer as an allowed grant type to cf-application-uaa. You can check that this grant type is added to the oAuth client with SAP BTP command line interface (btp CLI) as shown below.

> cf deploy cf-application_1.0.0.mtar -e mta-ext-ex3a.yaml -f --no-start
.. > btp get security/app cf-application!t53187 --subaccount $subaccount
.. grant-types: - urn:ietf:params:oauth:grant-type:jwt-bearer
.. > btp get security/app cf-approuter!t53187 --subaccount $subaccount
.. grant-types:
.. 

In this exercise an external application requests for an id token from the trusted SAP Cloud Identity Services tenant and then exchanges this token for one from XSUAA service. It is assumed that the external application has acquired the id token from SAP Cloud Identity Services already while authenticating the user and so there is no need for requesting authorization or credentials from the user again. Application Router plays no role in this scenario.

We will get an token from SAP Cloud Identity Services for this exercise as the first step. We will get this with OpenID Connect Authorization Code Flow. First we need to client credentials for to the trusted Cloud Identity Services tenant for this. Refer to the setup for integrating SAP AppGyver with SAP BTP for a detailed look at this setup.

Copy the contents below to file exercise-3a-ias.json:

{ "display-name": "Exercise 3a: JWT Bearer Token grant", "xsuaa-cross-consumption": true, "oauth2-configuration": { "redirect-uris": [ "http://localhost:3030/callback" ] }
}

Create a SAP Cloud Identity Services service instance with the name cf-external-ias, with the configuration in the file aexercise-3a-ias.json.

> cf create-service identity application cf-external-ias -c exercise-3a-ias.json
..
> cf create-service-key cf-external-ias key1
..
> cf service-key cf-external-ias key1 { "clientid": "0098768-b2d2-fff1-884b-dc87777773639", "clientsecret": "HguVBwnF67543BFCElK987U2El7c2=", "credential-type": "SECRET", "domain": "accounts.ondemand.com", "domains": [ "accounts.ondemand.com" ], "url": "https://at6ysjs7ioo.accounts.ondemand.com", "zone_uuid": "----------------------------"
}

Update the file .env with the content added as follows with values for key1 from command above.

  • ias_clientId as clientid
  • ias_clientSecret as clientsecret
  • ias_url as url

Sample for values in the file with values for key1 above is:

ias_xsappname=cf-approuter!t53187
ias_clientId=0098768-b2d2-fff1-884b-dc87777773639
ias_clientSecret=HguVBwnF67543BFCElK987U2El7c2=
ias_url=https://at6ysjs7ioo.accounts.ondemand.com ias_tokenEndpoint={{ias_url}}/oauth2/token
ias_authorizationEndpoint={{ias_url}}/oauth2/authorize
ias_scope=openid
ias_responseType=code
ias_redirectUri=http://localhost:3030/callback

Continue in the terminal entering the following command:

#bash
httpyac oauth2 --prefix ias --flow authorization_code > ias-id-token.txt
#powershell
httpyac oauth2 --prefix ias --flow authorization_code | Set-Content -Encoding ascii ias-id-token.txt

Note: The file has to be saved in utf-8 encoding.

Inspect the token:

cat ias-id-token.txt | node decode-jwt.js

Note that it is not issued by XSUAA service; issuer is Cloud Identity Service tenant, not XSUAA service.

Copy the contents below to file exercise-3a.http

### Get Token for Business Logic Application with IAS ID Token
# @name jwt_bearer {{ const fs = require("fs"); const jwt = fs.readFileSync(__dirname + "/ias-id-token.txt"); exports.assertion = jwt.toString('utf8').trim();
}} # @jwt access_token
POST {{blApp_url}}/oauth/token Content-Type: application/x-www-form-urlencoded
Accept: application/json
Authorization: Basic {{blApp_clientId}} {{blApp_clientSecret}} grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&assertion={{assertion}} ### Passcode
{{ const open = require("open") const passcode_url = `${blApp_url}/passcode` open(passcode_url);
}}
# @name passcode
# @jwt access_token
POST {{blApp_url}}/oauth/token Content-Type: application/x-www-form-urlencoded
Accept: application/json
Authorization: Basic {{blApp_clientId}} {{blApp_clientSecret}} grant_type=password
&passcode={{$password Enter Passcode}}

This file describes a request for access token using an id token from SAP Cloud Identity Services tenant. Lets make the request and see with the following command.

Start in the terminal and enter the command.

> npx httpyac exercise-3a.http -n jwt_bearer -o body === Get Token for Business Logic Application with IAS ID Token === { "access_token": "eyJhbGc............mdgg", "token_type": "bearer", "expires_in": 43199, "scope": "openid", "jti": "c60d8b7943b54f35a2a8646e3f885500", "access_token_parsed": { "jti": "c60d8b7943b54f35a2a8646e3f885500", "ext_attr": { "enhancer": "XSUAA", "subaccountid": "a0622234-xxxx-xxxx-xxxx-ff27b1b0ea49", "zdn": "my-ias-sandbox", "oidcIssuer": "https://at6ysjs7ioo.accounts.ondemand.com" }, "user_uuid": "128be729-f34d-40bd-a7a2-db1645776cf5", "given_name": "John", "xs.user.attributes": {}, "family_name": "Dow", "sub": "1fa1d178-xxxx-xxxx-xxxx-42b28f0c6f9e", "scope": [ "openid" ], "client_id": "sb-cf-application!t131271", "cid": "sb-cf-application!t131271", "azp": "sb-cf-application!t131271", "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", "user_id": "1fa1d178-e624-4ec1-8022-42b28f0c6f9e", "origin": "sap.custom", "user_name": "john.dow@my-sap.com", "email": "john.dow@my-sap.com", "rev_sig": "7a1c79bb", "iat": 1658909961, "exp": 1658953161, "iss": "https://my-ias-sandbox.authentication.eu10.hana.ondemand.com/oauth/token", "zid": "a0622234-xxxx-xxxx-xxxx-ff27b1b0ea49", "aud": [ "openid", "sb-cf-application!t131271" ] }
}

The script reads the previously saved id token from file and sends this as authorization for access token. Compare this token with a token when the same user logs in directly using password grant (with passcode).

> httpyac exercise-3a.http -n passcode -o body
=== Passcode === { "access_token": "eyJhb...,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4dRFY2X-eg", "token_type": "bearer", "id_token": "eyJhbGc...........................,,,,,,,,,,,,,jg", "expires_in": 43199, "scope": "openid", "jti": "3fabff31a05444898a8875fda1dd081d", "access_token_parsed": { "jti": "3fabff31a05444898a8875fda1dd081d", "ext_attr": { "enhancer": "XSUAA", "subaccountid": "a0622234-xxxx-xxxx-xxxx-ff27b1b0ea49", "zdn": "my-ias-sandbox", "oidcIssuer": "https://at6ysjs7ioo.accounts.ondemand.com" }, "user_uuid": "128be729-f34d-40bd-a7a2-db1645776cf5", "given_name": "John", "xs.user.attributes": {}, "family_name": "Dow", "sub": "1fa1d178-e624-4ec1-8022-42b28f0c6f9e", "scope": [ "openid" ], "client_id": "sb-cf-application!t131271", "cid": "sb-cf-application!t131271", "azp": "sb-cf-application!t131271", "grant_type": "password", "user_id": "1fa1d178-e624-4ec1-8022-42b28f0c6f9e", "origin": "sap.custom", "user_name": "john.dow@my-sap.com", "email": "john.dow@my-sap.com", "rev_sig": "7a1c79bb", "iat": 1658910886, "exp": 1658954086, "iss": "https://my-ias-sandbox.authentication.eu10.hana.ondemand.com/oauth/token", "zid": "a0622234-xxxx-xxxx-xxxx-ff27b1b0ea49", "aud": [ "openid", "sb-cf-application!t131271" ] }
}

Note that there is no difference, except for the property grant_type. The property user_uuid and the property sub have the same value in either case.

Assign role collection to the user, and repeat the steps.

# $subaccount is the id of the subacoount and # $user is the user id (usually an email) of the user
# in IDP sap.custom. # Here sap.custom is the key for the trusted OpenID Connect identity provider
btp assign security/role-collection Excercise_Role_Collection_1 \ --to-user $user --of-idp sap.custom --subaccount $subaccount

Note that, once the role collection is added, additional scopes appear in both cases. Token with jwt bearer grant with id token from trusted Open ID Connect identity provider represents the same user as the user logged in with password.

Side Note:

The token exchange examined here is automatically done by @sap/xssec library from version 3.1.2. So the external application can send the id token from SAP Cloud Identity Service to the a Business Logic Application using a version of this library that supports this exchange. This illustrates that such an exchange can be made implicitly by the Business Logic Application and this can be transparently done by standard libraries.

SAML2 Bearer (assertion)

We need to make changes to the deployment, enable the grant type SAML2 Bearer assertion for the XSUAA instance of Business Logic Application. So, deploy the mta again, now with an extension descriptor. The descriptor configures the xsuaa instances with the required grant type.

Copy the contents below as file mta-ext-ex3b.yaml

---
_schema-version: '3.1'
ID: cf-application-ext-3b
extends: cf-application
version: 1.0.0 resources: - name: cf-application-uaa parameters: config: oauth2-configuration: grant-types: - urn:ietf:params:oauth:grant-type:saml2-bearer - password - name: cf-approuter-uaa disabled: true parameters: config: oauth2-configuration: grant-types: []

Execute the following commands to deploy the package with the extension descriptor copied in the earlier step. The extension descriptor, adds saml2-bearer as a allowed grant type to cf-application-uaa. You can check that this grant type is added to the oAuth client with SAP BTP command line interface (btp CLI) as shown below.

cf deploy cf-application_1.0.0.mtar -e mta-ext-ex3b.yaml -f --no-start .. > btp get security/app cf-application!t53187 --subaccount $subaccount
.. grant-types: - urn:ietf:params:oauth:grant-type:saml2-bearer
.. > btp get security/app cf-approuter!t53187 --subaccount $subaccount
.. grant-types:
.. 

In this exercise an external application requests for a signed SAML2 assertion from a trusted SAML2 identity provider. It is assumed that the external application has acquired the authorization from the user and so there is no need for requesting authorization or credentials from the user again. Application Router plays no role in this scenario too.

Copy the contents below to file exercise-3b.http

### Get Token for Business Logic Application with signed SAML assertion
# @name saml2_bearer {{ const fs = require("fs"); const signedAssertion = fs.readFileSync(__dirname + "/saml-assertion.xml"); const assertionEncoded = Buffer.from(signedAssertion,'utf8').toString("base64") exports.assertion = encodeURIComponent(assertionEncoded);
}} # @jwt access_token
POST {{blApp_SAML2tokenEndpoint}}
Content-Type: application/x-www-form-urlencoded
Accept: application/json
Authorization: Basic {{blApp_clientId}} {{blApp_clientSecret}} grant_type=urn:ietf:params:oauth:grant-type:saml2-bearer
&assertion={{assertion}}

This file describes a request for access token using a signed SAML assertion from a trusted SAML based identity provider. Lets make the request and see with the following command.

Copy signed SAML assertion (xml) to file saml-assertion.xml. Refer Appendix A for how to create one if you have setup trust as detailed there.

Identify the token endpoint for saml2-bearer grant request. This URL is mentioned in SAML Metadata for SAP BTP subaccount (picture below). If you use the setup from Appendix A, this value is also printed out when generating signed SAML assertion.

Update .env file with an entry for blApp_SAML2tokenEndpoint with the value for token endpoint for saml2-bearer grant request as shown below.

blApp_SAML2tokenEndpoint=https://..authentication.us10.hana.ondemand.com/oauth/token/alias/... 

Start in the terminal and enter the command.

> npx httpyac exercise-3b.http -n saml2_bearer -o body
=== Get Token for Business Logic Application with signed SAML assertion === { "access_token": "eyJhbG.......................67SjiHFg", "token_type": "bearer", "expires_in": 43199, "scope": "openid", "jti": "b69886cfd81142879629687635084e7f", "access_token_parsed": { "jti": "b69886cfd81142879629687635084e7f", "ext_attr": { "enhancer": "XSUAA", "subaccountid": "a0622234-xxxx-xxxx-xxxx-ff27b1b0ea49", "zdn": "my-ias-sandbox" }, "given_name": "John", "xs.user.attributes": {}, "family_name": "Dow", "sub": "87fcd939-2dd2-4b9c-bb29-6cc89e27dff6", "scope": [ "openid" ], "client_id": "sb-cf-application!t131271", "cid": "sb-cf-application!t131271", "azp": "sb-cf-application!t131271", "grant_type": "urn:ietf:params:oauth:grant-type:saml2-bearer", "user_id": "87fcd939-2dd2-4b9c-bb29-6cc89e27dff6", "origin": "localhost.testing", "user_name": "john.dow@my-sap.com", "email": "john.dow@my-sap.com", "rev_sig": "d8b8b3fe", "iat": 1658930097, "exp": 1658973297, "iss": "https://my-ias-sandbox.authentication.eu10.hana.ondemand.com/oauth/token", "zid": "a0622234-xxxx-xxxx-xxxx-ff27b1b0ea49", "aud": [ "openid", "sb-cf-application!t131271" ] }
}

Note that even though there appears to be be no difference for this token from the token from earlier exercise, except for the property grant_type, the property user_uuid and the property sub have the different value. The property origin also has different value. The scope from the role assignment is also not present. This is a token for a different  user with almost same attributes.

Assign role collection to the user, and repeat the step. Note that previously generated SAML assertion is still valid, if it has not expired.

# $subaccount is the id of the subacoount and # $user is the user id (email) of the user
# Here localhost.testing is the key for the SAML identity provider
btp assign security/role-collection Excercise_Role_Collection_1 \ --to-user $user --of-idp localhost.testing --subaccount $subaccount

Note that, once the role collection is added, additional scopes appear.

Side Note:

The token exchange examined here is implemented by destination service in SAP BTP where it acts as the trusted identity provider. See documentation on  how to set up trust with destination service. When destination service is used as the SAML identity provider, it copies the user attributes from the trusted JWT token it receives as attribute statements in SAML assertion. The rules for this are mentioned in documentation (see section Propagate User Attributes.)

Key Learnings

  • Which grant types are required by Business Logic Application for enabling access from an external application with principal propagation.
  • The role of trusted identity provider in enabling principal propagation from an external application.
  • Similarity and differences between using jwt-bearer and saml2-bearer token grants.

Next Steps

Did you understand a bit more about XSUAA with this exercise? Please let me know in the comments.
Proceed with the other lab exercises to examine further:

Appendix A – setup a local SAML Identity Provider for the lab

Setup

Install dependencies

npm install -D saml xpath @xmldom/xmldom

Copy contents below to saml-idp-metadata.js

const fs = require("fs");
const {getSamlIdpMetadata} = require("./saml-localhost-idp"); if (require.main === module) { main( process.argv.slice(2) );
} function main(argv) { fs.writeFileSync("saml-idp-metadaa.xml",getSamlIdpMetadata());
}

Copy contents below to saml-signed-assertion.js

const fs = require("fs");
const {getSignedAssertion, extractFromSpMetadata} = require("./saml-localhost-idp"); if (require.main === module) { main( process.argv.slice(2) );
} function main(argv) { //https://${xsuaa.url}/saml/metadata const sp_metadata_xml = fs.readFileSync(__dirname + "/saml-sp-metadata.xml"); const user_attributes = require("./user_attributes"); const passphrase = process.env.samlidp_key_passphrase; const sp = extractFromSpMetadata(sp_metadata_xml) const signedAssertion = getSignedAssertion(sp,user_attributes,passphrase) fs.writeFileSync("saml-assertion.xml",signedAssertion);
}

Copy contents below to saml-localhost-idp.js

const fs = require("fs");
const { createPrivateKey, X509Certificate } = require('crypto');
const IDP_ENTITY_ID = "localhost/testing"; function getSamlIdpMetadata() { const pem = fs.readFileSync(__dirname + "/saml-idp.pem"); const cert = new X509Certificate(pem); IDP_CERTIFICATE = cert.raw.toString('base64') const samlidp_metadata = `<?xml version="1.0" encoding="UTF-8"?> <ns3:EntityDescriptor ID="${IDP_ENTITY_ID}" entityID="${IDP_ENTITY_ID}" xmlns="http://www.w3.org/2000/09/xmldsig#" xmlns:ns3="urn:oasis:names:tc:SAML:2.0:metadata"> <ns3:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> <ns3:KeyDescriptor use="signing"> <KeyInfo> <X509Data> <X509Certificate>${IDP_CERTIFICATE}</X509Certificate> </X509Data> </KeyInfo> </ns3:KeyDescriptor> </ns3:IDPSSODescriptor> </ns3:EntityDescriptor>` return samlidp_metadata;
} function extractFromSpMetadata(sp_metadata_xml){ const xpath = require('xpath'), dom = require('@xmldom/xmldom'); const doc = new dom.DOMParser().parseFromString(sp_metadata_xml.toString()) const select = xpath.useNamespaces({"md":"urn:oasis:names:tc:SAML:2.0:metadata"}) const SP_ENTITY_ID = select('/md:EntityDescriptor/@entityID',doc)?.[0].value if( ! SP_ENTITY_ID ) throw("Service Provider Metadata is invalid: attribute entityID is missing."); const TOKEN_ENDPOINT = select('/md:EntityDescriptor/md:SPSSODescriptor' + '/md:AssertionConsumerService[@Binding="urn:oasis:names:tc:SAML:2.0:bindings:URI"]'+ '/@Location',doc)?.[0]?.value if( ! TOKEN_ENDPOINT ) throw("Service Provider Metadata is invalid: service with URI Binding is missing."); return { entityId: SP_ENTITY_ID, tokenEndPoint: TOKEN_ENDPOINT }
} function getSignedAssertion(sp,attributes=null,passphrase=null) { const saml = require("saml").Saml20 cert = fs.readFileSync(__dirname + "/saml-idp.pem"), key = fs.readFileSync(__dirname + "/saml-idp.key") if(typeof passphrase == "string" & passphrase != "") { pk = createPrivateKey( {passphrase:passphrase, key: key} ); key = pk.export({format:"pem",type:"pkcs1"}); } const options = { nameIdentifier: attributes?.email || attributes?.mail || "system@localhost.earth", nameIdentifierFormat: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", attributes: attributes, issuer: IDP_ENTITY_ID, recipient: sp.tokenEndPoint, audiences: sp.entityId, authnContextClassRef: "urn:oasis:names:tc:SAML:2.0:ac:classes:PreviousSession", lifetimeInSeconds: 6000, //+300sec includeAttributeNameFormat: false, cert: cert, key: key }; return saml.create(options)
} exports.getSamlIdpMetadata = getSamlIdpMetadata;
exports.extractFromSpMetadata = extractFromSpMetadata
exports.getSignedAssertion = getSignedAssertion;

Note: The approach for generation of SAML assertion used here is described in How to generate SAML bearer assertion token for OAuth2SAMLBearerAssertion flow?

Generate key pair

The utility openssl comes preinstalled with linux and it is part (C:\Program Files\Git\usr\bin\openssl.exe) of Git for Windows installation. This can generate the the key pair required. The following command will generate a key pair valid for 30 days and will store the key without a passphrase.

> openssl req -new -x509 -days 30 -nodes -keyout saml-idp.key -out saml-idp.pem Generating a RSA private key
.+++++
..........................................+++++
writing new private key to 'saml-idp.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:.
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:.
Organization Name (eg, company) [Internet Widgits Pty Ltd]:COM
Organizational Unit Name (eg, section) []:Experimenting
Common Name (e.g. server FQDN or YOUR name) []:localhost/testing
Email Address []:

Establish trust with IDP

Run the command below.

node saml-idp-metadata.js

It generates a file saml-idp-metadata.xml which can be used to establish trust with this SAML Identity Provider in your SAP BTP subaccount as shown below.

Register SAP BTP Subaccount with IDP

Download the SAML Metadata for the subaccount and save it as saml-sp-metadata.xml. This can be done with the following command.

# copy the value of blApp_url from .env as ${blApp_url} below
curl -so saml-sp-metadata.xml ${blApp_url}/saml/metadata

This can also be downloaded from Trust Configuration section in SAP BTP cockpit.

Copy the downloaded file as saml-sp-metadata.xml in the directory created for the exercises.

Get signed SAML assertion

Update user attributes in file user_attributes.json. Maintain the attributes like for the user in SAP Cloud Identity Services tenant.

{ "Groups": ["administrators","accountants"], "first_name":"John", "last_name":"Dow", "mail":"john.dow@my-sap.com"
}

Run the following command to create a file named saml-assertion.xml with signed SAML assertion. The assertion is valid for 100 minutes.

> node saml-signed-assertion.js
SP Token Endpoint: https://my-ias-sandbox.authentication.us10.hana.ondemand.com/oauth/token/alias/my-ias-sandbox.aws-live-eu10