Skip to Content
Technical Articles

Principal Propagation between Azure AD and an On-Premise SAP System through SAP API Management

Scenario Overview

Single Sign-On is one of the most convenient features for users. This convenience however usually comes with a lot of integration effort and challenges on the implementation side. This is especially the case if we have different Identity Providers (IdP) and Trust setups involved.

This blog entry aims to guide you through one of these scenarios: Enabling principal propagation between an Azure AD as our IdP and an On-Premise SAP System through an API on SAP API Management.

For this we’ll make use of the great work done by Martin Raepple which he presented in his blog entry on Principal propagation in a multi-cloud solution between Microsoft Azure and SAP BTP. In our case we’ll have a more specialised scenario in terms of the xsuaa connection on SAP BTP side. However we have an additional component with the SAP API Management in between which will play an important role in this case.

The challenge we faced with the integration was that clients would call our endpoints exposed by API Proxies in the SAP API Management with an OIDC token issued by the Azure AD. This OIDC token however can’t be processed by the xsuaa in this form and therefore cannot be used for accessing the On-Premise connection. For the purpose of this blog we assume that the principal propagation setup from the BTP CF environment to the On-Premise system is already configured and tested. The main issue is to integrate this into our API flow in the API Proxy and exchange the Azure AD token for a token trusted by our xsuaa.

Solution Overview

The following picture gives an overview of the intended setup of the solution and the request flows in between:

Solution%20Overview%20and%20sequence

Solution Overview and sequence

We use the approach explained in the blog mentioned above to exchange the incoming OIDC token for a SAML token. That token can then be used to acquire a valid JWT token from our xsuaa instance. Due to the setup of the principal propagation we can use that token to forward the initial request to the backend system through the SAP Cloud Connector.

For this connection we use the On-Premise Connectivity Plan of the API Portal service as described in the help page of SAP API Management. After setting it up and creating a service key to get the credentials we’re ready to implement the API Proxy and its flow in the API Portal.

Prerequisites

To enable the flow which was previously outlined we need access to both the Azure AD instance and our SAP API Management API Portal instance. Of course these access permissions might be distributed across different people. As per Martin’s blog I linked to above this is (by my knowledge) currently only possible with Azure AD and the token exchange feature of it.

To create the needed on-premise-connectivity instance of the API Management and its service key you’ll also need the according permission and entitlement in your subaccount.

The most important step in the preparation is to setup the trust between the Azure AD instance and our xsuaa instance as described in Martin’s blog!

The complete list:

  • Azure AD instance and access to the service key creation
  • Trust setup between the Azure AD instance and our xsuaa instance
  • SAP API Management API Portal instance and Developer permissions on it
  • on-premise-connectivity instance service key or permission and entitlement to create it
  • (optional) an on premise system to test the setup with 😉

SAP API Management Artefacts

Since we don’t want to add the same steps for every API Proxy that wants to access On-Premise resources with Principal Propagation, we’ll implement it as a shared API Proxy similar to the one mentioned in this blog. This way it can be called from the other API Proxies in order to retrieve the valid token.

Preparation

For the needed calls to Azure AD we’ll first create a relatively simple API Provider with the configuration to connect to the Azure AD instance:

Azure%20AD%20API%20Provider

Azure AD API Provider

Second we’ll create an encrypted Key Value Map for the credentials we need. it contains details of three categories:

  • The shared secret to use the Shared Flow (see above)
  • Credentials from the on-premise-connectivity service instance key
  • Credentials from Azure AD

Key%20Value%20Map%20with%20needed%20credentials

Key Value Map with needed credentials

Sorry for the poor ordering of the key value map entries 🙂

API Proxy

Now for the API Proxy itself. The API Proxy we’re going to create is a so called Loopback API which does not actually forward the request to a target endpoint – all logic will be done in Service Callouts. For this we create a blank API Proxy and just one resource: /token The actual work then is done in the policies of the API Proxy.

Let’s look at the overview first – there’s two flows which we’re using: the PreFlow of the ProxyEndpoint to check whether the caller has our shared secret and the token flow of the ProxyEndpoint where we do the actual token conversion. The former can be taken from the blog I already linked above and is not part of this blog.

In total we use 8 policies – 7 on the Incoming request and 1 on the outgoing response:

Policy%20Flow

Policies in token flow

The first JS policy extracts the incoming JWT token issued by Azure AD and stores it in a variable:

// extract auth header to retrieve JWT Token
var reqAuth = context.getVariable("request.header.Authorization");

if (!reqAuth) throw 'Missing Auth Header';
    
var authParts = reqAuth.split(" ");

if (!authParts[0].toLowerCase() === "bearer" || authParts.length !== 2) throw 'Only OAuth Bearer Tokens Accepted'

if (!authParts[1] || !/^[a-zA-Z0-9\-_]+?\.[a-zA-Z0-9\-_]+?\.([a-zA-Z0-9\-_]+)?$/.test(authParts[1])) throw 'Only valid OAuth Bearer Tokens Accepted'

context.setVariable("private.jwttoken", authParts[1]);

Next we extract all needed values from our previously created encrypted key value map. For convenience all the local variables in the flow are called the same as the keys in the key value map (“private.<key value map key>”).

Using the credentials retrieved from the key value map and the incoming JWT token we now query the token endpoint of the Azure AD instance to exchange our JWT token for a SAML assertion. This equals the “Request SAML assertion from AAD with ObO flow” Postman request of the Postman collection in Martin’s blog. The code of the ServiceCallout policy makes use of the credentials and details of the Azure AD instance we just retrieved from the encrypted key value map. We’re using the API Provider which we previously created in the preparation as the HTTPTargetConnection of our request.

<ServiceCallout async="true" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
    <Request>
        <Set>
            <Headers>
                <Header name="Authorization">Bearer {private.jwttoken}</Header>
                <Header name="Content-Type">application/x-www-form-urlencoded</Header>
                <Header name="Accept">*/*</Header>
                <Header name="Accept-Encoding">gzip, deflate, br</Header>
            </Headers>
            <FormParams>      
    			<FormParam name="grant_type">urn:ietf:params:oauth:grant-type:jwt-bearer</FormParam>      
    			<FormParam name="client_id">{private.aad_client_id}</FormParam>      
    			<FormParam name="client_secret">{private.aad_client_secret}</FormParam>    
    			<FormParam name="resource">{private.aad_resource_id}</FormParam>    
    			<FormParam name="requested_token_use">on_behalf_of</FormParam>    
    			<FormParam name="requested_token_type">urn:ietf:params:oauth:token-type:saml2</FormParam>
    			<FormParam name="assertion">{private.jwttoken}</FormParam>
    		</FormParams>
    		<Verb>POST</Verb>
        </Set>
	</Request>
	<!-- the variable into which the response from the external service should be stored -->
	<Response>private.azuread.response</Response>
	<!-- The time in milliseconds that the Service Callout policy will wait for a response from the target before exiting. Default value is 120000 ms -->
	<Timeout>30000</Timeout>
	<HTTPTargetConnection>
        <APIProvider>INT0201_Azure_AD</APIProvider>
		<Path>/{private.aad_tenant_id}/oauth2/token</Path>
    </HTTPTargetConnection>
</ServiceCallout>

To retrieve the SAML assertion from the response we use the ExtractVariables policy.

<ExtractVariables async="false" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
    <JSONPayload>
        <Variable name="private.azuread.access_token" type="string">
            <JSONPath>$.access_token</JSONPath>
        </Variable>
    </JSONPayload>
    <Source>private.azuread.response.content</Source>
</ExtractVariables>

Additionally to retrieving the SAML assertion we also need to create credentials for our ServiceCallout to the xsuaa instance which will be the last of our service calls. For this we use the third set of variables retrieved from the key value maps: the credentials of our on-premise-connectivity service instance. For this we use the BasicAuthentication policy with the client id and secret.

<BasicAuthentication async='true' continueOnError='false' enabled='true' xmlns='http://www.sap.com/apimgmt'>
	<Operation>Encode</Operation>
	<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
	<User ref='private.uaa_client_id'></User>
	<Password ref='private.uaa_client_secret'></Password>
	<AssignTo createNew="true">sapapim.auth</AssignTo>
</BasicAuthentication>

Now we’re ready for the last call which will give us the token to be used when connecting to an on premise system.

<ServiceCallout async="true" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
	<Request>
	    <Set>
            <Headers>
                <Header name="Authorization">{sapapim.auth}</Header>
                <Header name="Content-Type">application/x-www-form-urlencoded</Header>
                <Header name="Accept">*/*</Header>
                <Header name="Accept-Encoding">gzip, deflate, br</Header>
            </Headers>
            <FormParams>      
    			<FormParam name="grant_type">urn:ietf:params:oauth:grant-type:saml2-bearer</FormParam>
    			<FormParam name="assertion">{private.azuread.access_token}</FormParam>
    		</FormParams>
    		<Verb>POST</Verb>
        </Set>
	</Request>
	<Response>sapapim.oauthresponse.token</Response>
	<Timeout>30000</Timeout>
	<HTTPTargetConnection>
		<URL>https://{private.uaa_token_domain}</URL>
    	<SSLInfo>
    	    <Enabled>true</Enabled>
    	    <ClientAuthEnabled>false</ClientAuthEnabled>
    	    <KeyStore/>
    	    <KeyAlias/>
    	    <TrustStore/>
    	</SSLInfo>
	</HTTPTargetConnection>
</ServiceCallout>

The SAML assertion of the previous step is passed to the xsuaa instance which (because of the previously set up trust relationship) returns a valid JWT token for this xsuaa instance.

Lastly we retrieve the token from the response and assign the corresponding fields to our response so the caller can use it. The latter is done in an AssignMessage policy on the response flow of the token flow.

<ExtractVariables async="false" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
    <JSONPayload>
        <Variable name="private.btp.access_token" type="string">
            <JSONPath>$.access_token</JSONPath>
        </Variable>
    </JSONPayload>
    <Source>sapapim.oauthresponse.token.content</Source>
</ExtractVariables>
<AssignMessage async="false" continueOnError="false" enabled="true" xmlns='http://www.sap.com/apimgmt'>
	<Set>
		<Payload contentType="application/json" variablePrefix="@" variableSuffix="#">{"auth_token":"@private.btp.access_token#"}</Payload>
	</Set>
	<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
	<AssignTo createNew="false" type="response">response</AssignTo>
</AssignMessage>

Usage

So how are you going to use the newly created Shared Principal Propagation API Proxy? You can just integrate it as a ServiceCallout into any other API Proxy on your instance e.g. like this:

<ServiceCallout async="true" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
	<Request>
		<Set>
		    <Headers>
				<Header name="Authorization">Bearer {request.header.Authorization}</Header>
				<Header name="secret">{private.shared.secret}</Header>
			</Headers>
			<Verb>POST</Verb>
		</Set>
	</Request>
	<Response>sapapim.accessToken</Response>
	<Timeout>15000</Timeout>
	<LocalTargetConnection>
   	    <Path>/v1/shared/principalpropagation/token</Path>
	</LocalTargetConnection>
</ServiceCallout>

Of course you can also use the Copy functionality of the AssignMessage policy to create the request to the Service Callout. /v1/shared/principalpropagation in this case is the base path of the Shared Principal Propagation API Proxy I created and of course you need to add the resource /token as well.

Summary

Using the great blog by Martin as our blueprint we applied the principle to our specialised use case of doing this in SAP API Management. Since SAP API Management specifically provides a service plan for the on premise access with the on-premise-connectivity service plan, we don’t need to dynamically set our target xsuaa instance but can always use the one connected to that instance.

Outlook

Of course there are some things that could be improved about this setup like error handling on false or illegitimate requests. If you spot anything feel free to point it out in the comments!

Thanks

At this point I’d also like to thank Tobias Pahlings for his help with all of the security knowledge needed for this integration.

11 Comments
You must be Logged on to comment or reply to a post.
  • Hi Niklas,

    I do not find the reason of introducing the step “4” in your scenario, why do you want/need to get the SAML from Azure AD?, you are generating a big dependency with that IDP, and impacting the performance of the API.

    From my point of view you just need to generate the SAML directly in SAP APIM, and with that you are adding a layer of trust between SAP APIM & XSUAA, that can be always be reused and it’s under your control, and of course you are improving the performance of your APIs a lot ( you are saving one extra call, and you do not need to maintain credential in you SAP APIM).

     

    Max.

    • Hi Maximiliano,

      We actually considered what you describe but there is a huge downside to the that approach: If you generate the SAML in the SAP APIM you would need the corresponding certificate to sign them. Having that certificate in the tenant would also mean, that you could basically create a SAML for anyone you would like - even not your own user.

      With the approach we chose we delegate that to the Azure AD since it is the central IdP in use in our scenario. It can validate the JWT token and will only give us a SAML for the user with a valid JWT token.

      For a more detailed explanation Tobias Pahlings can help as he is the security expert in our setup. He's also the one who advised against generating the SAML in our SAP APIM instance for the reasons mentioned.

      BR,
      Niklas

      • Hi Niklas,

        That "downside" that you mentioned is the main purpose of the "SAML Policies" in SAP API Management.

        I would say that step "4" is there because someone wanted it there, but it is not really necessary, I will not open a debate here 🙂

         

        Max.

    • Hi Max,

      I agree with Niklas. Generating tokens (such as SAML assertions) should remain the responsibility of the Identity Provider. And don't underestimate the effort of generating tokens. Besides finding a secure place for the credentials for all cryptographic operations (signature, encryption), SAML assertions in particular bear a certain complexity due to their flexible content model and federation concepts.

      From an architectural perspective, creating a dependency to a central identity provider is actually a prerequisite to achieve single sign-on and consistent user management in a distributed system scenario such as the one Niklas is describing here or I've been working on.

      Best regards

      Martin

      • Hi Martin,

        In the real world ( not in paper ) this is too expensive from the api performance point of view, following this concept you must generate an extra http call per every API call where you need principal propagation.

        From my point of view the level of trust between “SAP APIM &  XSUAA” must be the same as “SAP Cloud Connector & Backend Servers”, it’s exactly the same scenario.

         

        Max.

        • Hi Max,

          looking more closely at the message flow in the diagram of Niklas scenario, I am actually thinking if the following sequence would make more sense (and also resolve possible performance issues): Let the Application Client do steps 1, 3, 4 and 5. After that, the Client has a valid JWT token from XSUAA which can be used to authenticate and authorize step 2 when calling the API Proxy. Assuming that the API Proxy is a stateless component, caching of the (SAML) token is not an option to improve performance. The Application Client might be better suited here and could use the refresh token from XSUAA to request a new access token once it expires. Niklas Miroll Tobias Pahlings What do you think?

          Best regards

          Martin

          • Hi Martin,

            Two things:

            1. SAP APIM is able to cache whatever you need, but XSUAA will not accept the same SAML token twice.
            2. It’s a really bad idea expose XSUAA service to the “public/external apps”

            I do not think that the entire flow must change here, the only part the is “questionable” here is that step “4”, and of course it can be improved from the security point of view:

            In this scenario:

            • How do you know that the real client application is making the request and not some else who has access to the OIDC token?
            • How do you know that the issuer of the OIDC token is allowed for the client application? ( in the real life you could have more than one IDP, and you would only allow the consumption of the APIs to specific client apps presenting OIDC from specific issuer).
            • How do you know that the OIDC token was issued for that client application and not for another application in your landscape?( in the real life the IDP is issuing OIDC token for different applications in your landscape, and people could try to reuse them)
            • How can you improve the performance of your API calls if every time that SAP APIM is receiving an API call you must validate OIDC token?

            You can take a look here if you are interested to know the answers.

            Max.

             

          • Hi Max,

            XSUAA's token endpoint is public by default (as well as other endpoints for SAML/front-channel SSO). Is there a way to make them "private" in your subaccount and put API management in front of it?

            Step 4 in the token exchange flow requires an authorized OAuth client in Azure AD to request the SAML assertion from Azure AD for the user. This requires the Azure AD admin to register an applications in Azure AD and creating a secret. Both values must be known (and securely stored) by the client application. If the client application runs on BTP, the destination service serves as a good place to store such values for example.

            When requesting the SAML assertion, Azure AD verifies the incoming OAuth access token. Only an access token issued by itself (i.e. the Azure AD tenant identified by the iss(uer) attribute) will be accepted. The access token also contains an aud(ience) attribute which identifies the Client Application by its client id. If the request is not authorized by the same client id (and valid secret), Azure AD will reject it. In other words: Only the client application the initial OAuth access token has been issued for can also request a SAML assertion from Azure AD for the authenticated user. More details on this flow in Azure AD can be found here.

            Regarding the performance concerns, I'd recommend to let the Client Application orchestrate the initial token exchange flow (AAD access token -> SAML assertion -> XSUAA access token + refresh token) as mentioned in my previous comment. The client could use the XSUAA access token to authorize calls to BTP services/APIs until it expires, and then use the refresh token to obtain a new access token from XSUAA.

            Thanks for sharing your blog post. I took a look at it, and if I understood it correctly, you are using a slightly different approach:

            • The (Client) Application uses the SAML response message (and not just a SAML assertion) from the initial SAML-based front-channel SSO to request the access token from APIM. RFC 7522 (SAML Bearer Grant Type) defines a SAML assertion to be used for this request, but I think this is only a minor issue.
            • More critical to me seems to be the fact that the SAML response (and the included SAML assertion) is actually consumed twice: First time by the client application for front-channel SSO, and then next to request the access token for calling the API on BTP. I was wondering how you handle the different (SAML) audiences? In a real-world setup, the client application and the downstream API/backend service will be different SAML service providers, and the SAML response sent by Azure AD will be issued for the (Client) application only (identified by the Audience and Recipient URL in the SAML Assertion). How did you solve this in your blog post setup?

            Best regards

            Martin

        • Hi Max,

          although I agree with your statement that it is expensive performance wise, from a security and risk perspective APIM and Cloud Connector are two very different systems with a completely different purpose.

          Cloud Connector (as well as an IDP like Azure AD) have a built-in functionality to create tokens like the JWT or the X509 Certificate. These applications were designed with security in mind while the average API flow in APIM typically does not have a threat model or regular security tests.

          Therefore, the goal is to keep the attack surface as low as possible by leveraging security functionality that is available out of the box.

          Keep in mind that you can do many things wrong when creating SAML tokens and validate JWT by hand. As a general rule, it is never advisable to write your own security functions unless you are an expert.

          The risks in storing a universal token that grant you full access to all your connected backend systems in an environment where many people have access is typically not worth the benefits. But of course there are other methods to limit the scope or building a similar scenario without the need to reach out to Azure. These measures would then change the risk perspective, but this depends on many factors.

          BR
          Tobias

          • Hi Tobias,

            Not sure if you have experience with SAP APIM, but In SAP API Management:

            • The attack surface is the "PreFlow of the ProxyEndpoint", this is the place where you must put all your policies to protect your APIs, in Niklas scenario the validation of the OIDC token
            • The safe zone is the "Preflow of the TargetEndpoint", this is the place where you know that authentication is already validated, and in case of "user context" APIs( principal propagation is required ) you must have a UPN.
              • This UPN you can have it from very different places, in Niklas scenario is inside of the OIDC token, and that UPN is the value that must be propagated to XSUAA via SAML
              • XSUAA will validate that the SAML is "trusted" and It will extract the UPN, generating another token( it could be JWT or SAML, depending of your configuration) for SAP Cloud Connector
              • SAP Cloud Connector will validate the token issued by XSUAA is "trusted" and it will extract the UPN, generating a short lived x509 certificate for backend servers( again depending of your configuration)
              • Backend server will validate the x509 ceritificate issues by SAP Cloud Connector is "trusted" and finally map the CN of the certificate to an username.

            As you can see nobody is talking about "storing a universal token that grant you full access to all your connected backend systems", we are talking about principal propagation here 😉

            Max.

            /
            😉
          • Hi Max,

            I am not sure if I understand your scenario correctly, but the problem lies in the generation of the SAML token inside the APIM. This forces you to have the required key material to sign the SAML token inside the service, being the "universal token". Maybe my choice of words was a bit misleading here.

            If I now compare Niklas's scenario to the one proposed by you, Niklas uses an external service to do the validation and generation of the tokens. This lifts the requirement to store security relevant material out of  the APIM to a service that is dedicated for such operations.

            I am not saying that the Azure AD Scenario is always the best way of doing it and depending on your scenario it might definitely be problematic with performance, but it is also a method where you do not need to trust all your developers inside the APIM. Another example might be that you do not need to roll over all your keys after someone with access to the APIM left your company. (And I know that this is never done at companies regardless 😉 ).

            I am just arguing that if you are storing such strong credentials (signing keys) inside the APIM (or any other platform accessed  on a regular basis) you need to make absolutely sure that the token can only be used in the scenarios you intend them to be used.

            I definitely do not want to sound like a security absolutist here, as I know the challenges and also the need for compromises between security and functionality. Only thing I want to point out is that you need to consider very careful if the risks are greater than the benefit. (And this is definitely very dependent on the type of application you want to support).

            I hope that clarifies my standpoint a bit.

            BR

            Tobias