Wednesday, February 8, 2017

How to consume an On-Premise REST service via SAP API Management and SAP HCI (HCP-IS) using JWT for authorization and authentication

In this blog, I want to share how we could a invoke a REST call on an on-Premise SAP Hybris commerce system from SAP API Management using JWT ( JSON Web Tokens) via SAP HCI (HCP-IS). This blog focuses on the configuration done on SAP API Management & SAP HCI.
If you want more information on the configuration of SAP Hybris Commerce System or enabling JWT for authorization and authentication, check the official documentation on SAP API Management and JSON Web Tokens.



This scenario is about an App (mobile or desktop) to search products on an SAP Hybris system via an API which is published on SAP API management using a JWT token.

Mobile/Desktop App

Upon registering a device/user with our services we generate a token for the device/user.

The App passes the JWT token in the HTTP Header as "Authorization" to the API proxy.


SAP API Management

Create an API proxy which is coupled with a service on HCI. As we know an API is exposed in SAP API Management as an API Proxy which further decouples an API from any backend changes.Go to SAP API Management API portal and click on Develop as shown in screen print below.




To create an API Proxy directly (without an API Provider) click on create API and enter the details of the SAP HCI tenant.



Later in this blog, we will configure an iFlow on HCI which has sender channel as HTTP. Since it will be an HTTP adapter on HCI system the endpoint of the HCI iFlow would be https://<host>:<port>/http/<path>

Use this HCI URL in the following screen. In the API Base Path field, provide a path prefix for the API. for example, /api


Click Add in the Resources tab and give the title and path prefix as "products/search". Select GET method and un select all the other methods as we are interested in only fetching the data .We will use this path prefix "products/search" in defining the address of the HCI HTTP sender channel.




Policies

A policy is a program that executes a specific function at runtime. Policies enable to augment your API with sophisticated features to control traffic, enhance performance, transform, enforce security, and increase the utility of your APIs. 

More info on policies here

Flow

A Flow defines a processing pipeline which controls how the API behaves and defines what information it should carry. A processing pipeline comprises of a Request and a Response stream. Proxy endpoint and target endpoint define a pipeline to process request and response messages. 

More info on flows here

In other words a Proxy endpoint refers to a URL which is exposed to clients (mobile/desktop Apps) and Target endpoint refers to a URL of the backend service. Each request or response flow is subdivided into:

PreFlow: Executed for every service configured on the API and before the ConditionalFlow.

ConditionalFlow: Executed when the condition associated with the flow is met.

PostFlow: Executed for every service configured on the API and after the ConditionalFlow.

PostClientFlow: This is an optional flow that is executed after the response message has been sent to the requesting client application.

Navigate to the Policies section by clicking on the Policies in edit mode.



Select the PreFlow and assign the following policies:

Extract Variable Policy:
API Proxy reads this Header key "Authorization" from the HTTP Header of the incoming request and converts to Header key called "JWT-Authorization". This is done because we also need the  "Authorization" header with HCI credentials in the message to logon to HCI. In this step we extract the JWT token which is passed from the APP in the Header key "Authorization" and assign it in variable called "jwt".

 <!-- Extract content from the request or response messages, including headers, URI paths, JSON/XML payloads, form parameters, and query parameters -->  
 <ExtractVariables async="true" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">  
      <Header name="Authorization">  
           <Pattern ignoreCase="true">{jwt}</Pattern>  
      </Header>  
      <Variable name="jwt"/>  
      <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>  
 </ExtractVariables>  

Assign Message Policy:
In this step a new Header key "JWT-Authorization" is created and value of variable "jwt" is assigned to it. Next to that also delete the header key "Authorization".

 <!-- This policy can be used to create or modify the standard HTTP request and response messages -->  
 <AssignMessage async="false" continueOnError="true" enabled="true" xmlns='http://www.sap.com/apimgmt'>  
    <Remove>    
         <Headers>     
                <Header name="Authorization"></Header>    
      </Headers>    
      </Remove>   
        <Add>    
           <Headers>     
                <Header name="JWT-Authorization">{jwt}</Header>    
           </Headers>    
   </Add>  
      <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>  
      <AssignTo createNew="false" type="request">request</AssignTo>  
 </AssignMessage>  

Assign Message Policy
This time we use a new Assign message policy to assign the HCI credentials. We use the basic authentication to logon to HCI, so user and password is assigned to variables "basicAuth.username" and "basicAuth.password".

 <!-- This policy can be used to create or modify the standard HTTP request and response messages -->  
 <AssignMessage async="false" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">  
      <AssignVariable>  
           <Name>basicAuth.username</Name>  
           <Value>s000000000</Value>  
      </AssignVariable>  
      <AssignVariable>  
           <Name>basicAuth.password</Name>  
           <Value>ACOREL</Value>  
      </AssignVariable>  
      <!-- Sets a new value to the existing parameter -->  
      <Set>  
           <Payload contentType="application/json" variablePrefix="@" variableSuffix="#">{&quot;name&quot;:&quot;foo&quot;, &quot;type&quot;:&quot;@apiproxy.name#&quot;}</Payload>  
      </Set>  
      <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>  
      <AssignTo createNew="false" type="request">request</AssignTo>  
 </AssignMessage>  

Basic Authentication Policy
Update this policy with the user and password variables created above.

 <BasicAuthentication async='true' continueOnError='false' enabled='true' xmlns='http://www.sap.com/apimgmt'>  
       <!-- Operation can be Encode or Decode -->  
      <Operation>Encode</Operation>  
      <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>  
       <!-- for Encode, User element can be used to dynamically populate the user value -->  
      <User ref='basicAuth.username'></User>  
       <!-- for Encode, Password element can be used to dynamically populate the password value -->  
      <Password ref='basicAuth.password'></Password>  
       <!-- Source is used to retrieve the encoded value of username and password. This should not be used if the operation is Encode-->  
       <Source>request.header.Authorization</Source>  
       <!-- Assign to is used to assign the encoded value of username and password to a variable. This should not be used if the operation is Decode -->  
      <AssignTo createNew="false">request.header.Authorization</AssignTo>  
 </BasicAuthentication>  

Request Cache
This policy is used to cache the data from the backend for 300 seconds so as to reduce the number of requests to the response. This way app's calling the same URI can use this policy to return cached response thus improving the performance

 <ResponseCache async="false" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">  
      <CacheKey>  
           <Prefix/>  
           <KeyFragment ref="request.uri" type="string"/>  
      </CacheKey>  
      <ExpirySettings>  
           <TimeoutInSec>300</TimeoutInSec>  
      </ExpirySettings>  
      <SkipCacheLookup>request.header.InvalidateCache="true"</SkipCacheLookup>  
      <SkipCachePopulation/>  
 </ResponseCache>  


TargetEndpoint Flow
One of the requirement is that, when the APP does not pass any query string, we have to default to fields returned in response to "product code" and "product name". This is done executing JavaScript while the Flow is being processed.

JavaScript Policy:
Add script file listparameters.js in the Scripts section and reference this file in the policy below. Following script adds the following "&fields=products(name,code)" to query string when the Request does not contain them.


 var Turl = context.getVariable("target.url");  
 var Ruri = context.getVariable("request.uri");  
 var Rpath = context.getVariable("request.path");   
 var qs = context.getVariable("request.querystring");  
 if( qs.indexOf("fields") <= -1 ){   
  context.setVariable("target.url", Turl+Rpath.slice(5)+"?"+context.getVariable('request.querystring')+"&fields=products(name,code)")    
 }  


SAP HCI

Create an Iflow with Sender Channel with adapter type as HTTPS and transport protocol HTTPS.


Adjust the address of the sender channel as "/products/search".


Define the receiver channel with adapter type as HTTP as follows.




Enter the header name "JWT-Authorization" in HCI runtime configuration . This enables to retain the header value from the incoming message from API.

Using Script message transformer in HCI iFlow, convert the Header value "JWT-Authoriation" to "Authorization" by assigning the following JS.

 importClass(com.sap.gateway.ip.core.customdev.util.Message);  
 importClass(java.util.HashMap);  
 function processData(message) {  
      //headers       
      var map = message.getHeaders();  
      var jwt = map.get("JWT-Authorization");       
      message.setHeader("Authorization", jwt);   
      message.setHeader("JWT-Authorization", null);  
      return message;       
 }  

Testing the API

To test the API using API Test console , choose the API proxy and provide the header key "JWT-Authorization" and its corresponding token value and hit send to see the results.





Debugging the API


We can activate debugging on API from either the Development or the test console.

Go to API development page and select the API proxy tab and display the API you want to debug. Click on debug api and eventually on "Start Debugging".





Call your service and click on refresh button to display details of the each step processed in various flows. More samples on SAP API management on GitHub here.

Enjoy APIng..

No comments:

Post a Comment