Skip to Content

API Token via Http Lookup in Adapter Module

Introduction

Token based authentication is prominent everywhere on the web nowadays. With most every web company using an API, tokens are the best way to handle authentication for multiple users.

In this blog I want to share a simple and generic adapter module implementation about how to obtain such a token and store it in the dynamic configuration for further use.

Additionally there’s an option to cache the token for subsequent calls. This is useful if you have loads of requests and you want to save some traffic or if the number of tokens (logins) that can be obtained per hour/day is limited.

We’re using this module in several REST and SOAP scenarios to request an Auth-Token that is needed in the HTTP Header of a HTTP Request. The caching function allows reusing a token within multiple XI-Messages, so the login is only done once in a specified time frame.

Implementation

In all our scenarios we had to put an Auth-Token in the HTTP Header of the request. HTTP-Header modification is possible in HTTP_AAE, REST and SOAP (AXIS) adapter. In every adapter you can access values from the Dynamic Configuration and put it into the HTTP Header. (examples are listed below)

In his very good blog post “Setting Dynamic Configuration Attributes Using Custom Adapter Module in Advanced Adapter Engine of PI/PO“,  is describing how to modify the dynamic configuration of an XIMessage. The provided classes to access the dynamic configuration can be extended and this is what we do here. So before you continue reading, it’s a good idea to read Vadims post first. 😉

The following picture highlights all classes that have been added to the project.

 

  • DynamicConfigurationProviderHttpLookup: Implements DynamicConfigurationProvider and contains the main logic. The following extract shows the idea of what is behind:

    if (storageEnabled) {
    		[...]
    		parameterValue = keyValueStore.get(storageKey);
    		[...]
    }
    
    if(parameterValue == null){
    	[...]
    		HttpRequest request = new HttpRequest(parameters);
    		HttpResponse response = HttpClient.doRequest(request, parameters);
    		parameterValue = response.getResponse(parameters);
    		[...]
    		if (storageEnabled) {
    			[...]
    			keyValueStore.add(storageKey, parameterValue,
    					storageExpirationTime);
    			[...]
    		}
    }
    [...]
    DynamicConfigurationAttribute dcAttribute = 
    		new DynamicConfigurationAttribute(parameterNamespace,
    				parameterName, parameterValue);
    if (dcAttribute.isDynamicConfigurationAttributeComplete()) {
    		dcAttributes.add(dcAttribute);
    }
    [...]
    
  • KeyValueStore: Wrapper class for the MessageIDMapper class. Provides add, get and remove method. The MessageIDMapper is used to persist data in PI Tables for a specified time. If you want to have a look at the raw data, open a SQL Browser an go to table SAPJ2EE.XI_AF_SVC_ID_MAP.
  • …util.http Package: Contains Helper Classes for HTTP-Requests.

Usage

Module Processing Sequence

  • Number: <before the module you want to use the token, usually somewhere at the beginning>
  • Module Name: Custom_AF_Modules/AddDynamicConfigurationBean
  • Type: Local Enterprise Bean
  • Module Key: <up to you> (e.g. dclookup)

Module Configuration

Parameter name Description
*class DynamicConfigurationProviderHttpLookup
*dc.attribute.name The name of the attribute in the Dynamic Configuration where you want to save the value received by the lookup.
*dc.attribute.namespace The namespace of the attribute in the Dynamic Configuration where you want to save the value received by the lookup.
*dc.http.request.url The URL that should be called (http://… , https://…).
dc.http.request.proxyHost If you need to route the call via a proxy, enter the Host-name of the Proxy-server here.
dc.http.request.proxyPort If you need to route the call via a proxy, enter the Port of the Proxy-server here.
dc.http.request.method HTTP Method: Only GET or POST are implemented yet. Default is POST.
dc.http.request.header.<custom>

If you need to add specific HTTP Header you can add them here. The <custom>-Tag has to be replaced by a custom name. As a value you can enter what you need, make sure to keep the following syntax:

<httpHeaderKey>: <httpHeaderValue>

e.g. If you need to add a SOAPaction you can do something like:

ParameterName: dc.http.request.header.MySoapAction

ParameterValue: SOAPAction: A_Custom_SOAP_ACTION

dc.http.request.postdata POST-Data is only available when you do a HTTP-Post. Enter the payload that you want to sent in the request.
dc.http.request.connectiontimeout HTTP connection timeout in ms, default is 60000s.
dc.http.request.readtimeout HTTP read timeout in ms, default is 60000s.
dc.http.response.valuesource It may happen, that the HTTP Response returns more information than you want to store in the dynamic configuration. This parameter accepts “Reqex” or “XPath“. If set, the selected operation is applied on the HTTP-Response.
dc.http.response.valuesource.xpath If dc.http.response.valuesource=XPath, enter the Xpath of the value you want to have.
dc.http.response.valuesource.regex If dc.http.response.valuesource=Regex, enter the Regex of the value you want to have.
dc.http.response.stringformat It may also happen, that you need to format the token somehow before further use. This is just a simple String.format() operation on the http/xpath or regex result.
[String.format(<your value>, value)] As an idea, you can enter something like “Bearer %s“.
dc.keyValueStore.enabled As already mentioned above, the module is able to store the received token for subsequent calls. This is enabled by setting the parameter to true. Default is false.
dc.keyValueStore.key This parameter is optional and by default initialized with the Channel-ID. It is possible to make several channels use the same storage by using the parameter. This is useful if the same token can be used in several receiver channels. E.g. If you have multiple receiver channels pointing to the same host and all can share the same token.
dc.keyValueStore.expirationTime Enter a value in minutes. Default is 1 minute.
dc.keyValueStore.clear By setting this to “true“, it will disable the KeyValueStore and removes the saved token. This is useful, if you need to remove a saved token for any reason.

(* mandatory)

Http header manipulation

Once a token is received, it can be used wherever the Dynamic Configuration is accessible. The following examples show, how to add an Auth-Token in the HTTP Header of a request.

HTTP Adapter 

  • In Tab “Advanced” check “Set Adapter Specific Message Properties” and “HTTP Header Fields”
    • e.g. in Field 1 (HeaderFieldOne): AuthKey

  • In the Modul Configuration (Custom_AF_Modules/AddDynamicConfigurationBean)
    • dc.attribute.name= HeaderFieldOne (… HeaderFieldSix)
    • dc.attribute.namespace = http://sap.com/xi/XI/System/HTTP_AAE

SOAP Adapter (AXIS Mode)

  • Have a look at the XI30DynamicConfigurationHandler for accessing the Dynamic Configuration in the SOAP Adapter.
  • The Note: “1039369 – FAQ XI Axis Adapter” shows some examples as well (search for ASMA)

REST Adapter

  • In Tab “REST URL” use Pattern Variable Replacement
    • Value Source: AdapterSpecific Key
    • Pattern Element Name: AuthKey
    • Adapter Specific Attribute: CustomAttribute
    • Attribute Name: AuthKey

  • In Tab “HTTP Headers” add a new row:
    • Header Name: e.g. Authorization
    • Value Pattern: {AuthKey}

  • In the Modul Configuration (Custom_AF_Modules/AddDynamicConfigurationBean)
    • dc.attribute.name= AuthKey
    • dc.attribute.namespace= http://sap.com/xi/XI/System/REST

Source

You can find the sources in my fork of Vadim’s Git-Repository here:

https://github.com/MartinBuselmeier/sap-xpi-adapter-module-add-dynamic-configuration

 


Et voilà! A generic module to request tokens with caching option.


 

Inheriting from DynamicConfigurationProviderHttpLookup

As you see, there are a lot of parameters to fill. Sometimes it makes sense to create a more specific implementation that fits to a special software. This makes it easier for PI Admins to maintain the Module Configuration in the channels. The following code is extracted from DynamicConfigurationProviderDemoSoftware class.

It shows how to extend the class DynamicConfigurationProviderHttpLookup and sets additional properties that are not specified by a module property by keeping the possibility to overwrite them if needed.

public class DynamicConfigurationProviderDemoSoftware extends
		DynamicConfigurationProviderHttpLookup {
	
	private static final String PARAMETER_USERNAME = "DemoSoftware.username";
	private static final String PARAMETER_PASSWORD = "DemoSoftware.password";
	private static final String PARAMETER_URL = "DemoSoftware.url";

	@Override
	public List<DynamicConfigurationAttribute> execute(Message message,
			Map<String, String> parameters)
			throws DynamicConfigurationProviderException {

		String username = "";
		String password = "";
		String url = "";
		for (Map.Entry<String, String> parameter : parameters.entrySet()) {
			if(parameter.getKey().equals(PARAMETER_URL)) {
				url = parameter.getValue();
			} else if (parameter.getKey().equals(PARAMETER_USERNAME)) {
				username = parameter.getValue();				
			} else if (parameter.getKey().equals(PARAMETER_PASSWORD)) {
				password = parameter.getValue();
			} else {

			}
		}
		String postdata = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
			+ "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">"
			+ "<soapenv:Header/>"
			+ "<soapenv:Body>"
			+ "<ns:Login>"
			+ "<ns:userName>"
			+ username
			+ "</ns:userName>"
			+ "<ns:password>"
			+ password
			+ "</ns:password>"
			+ "</ns:Login>"
			+ "</soapenv:Body>" + "</soapenv:Envelope>";

	// Set default values if not already set by Module Parameter
	if (!parameters.containsKey("http.request.url"))
		parameters.put("http.request.url", url);
	
	if (!parameters.containsKey("http.request.postdata"))
		parameters.put("http.request.postdata", postdata);
	
	if (!parameters.containsKey("http.request.header.soapAction"))
		parameters.put("http.request.header.soapAction","SOAPAction: \"Login\"");
	[...]
	return super.execute(message, parameters);
	}
}

The next picture shows the module configuration for the DynamicConfigurationProviderDemoSoftware class.

That’s it 🙂

 

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

    Thank you for sharing this development with the community – very impressive and well-considered work!

    Together with exploring feasibility of manipulation of dynamic attributes of the message from within the adapter module sequence execution, I was involved in a project, were we finally used that development in a practical way, given the requirement of using OAuth 2.0 authentication mechanisms when sending requests from PO to an external system (originally applying this enhancement to HTTP_AAE adapter, later on switching to REST adapter), which is another use case of setting additional HTTP header with dynamically determined header value. I’ve documented the approach here – but, I shall admit, the design and implementation is intended for low/medium volume scenarios with no involvement of any additional persistence layer on PO side, hence there is no OAuth key persistence in PO, and every processed message triggers acquisition of the access token without using token refreshment / invalidation techniques.

    Furthermore, I can evidence recent enhancements and improvements introduced in functionality of the REST adapter – for example, support of some OAuth 2.0 grant types for PI/PO release 7.5 (refer to SAP Note 2405166), so we may get some features from this area, that we currently have to develop as custom functions (like within custom adapter modules), being a part of SAP standard in the future. Having written so, I shall say that flexibility of your implementation definitely allows its wider application that goes beyond specific authentication framework / mechanism usage.

    Regards,

    Vadim

  • I have never added a module to PO 7.40.  Can explain how to deploy your module or point me to an example of how to do it?

    All help is appreciated.

    David

  • Hi,

    I know it’s pretty late since this blog was written, but I face an issue when I try to use this module in PI system. Could someone please advise whats wrong here?.

    I deployed the module using NWDS to my 7.4 system.

    My scenario is File -> PI -> REST.

    I have configured module tab as mentioned in this blog, but i received below mentioned error when tested.

    Exception caught by adapter framework: Cannot cast class com.sun.proxy.$Proxy3840 to interface com.sap.aii.af.lib.mp.module.ModuleLocal (found matching interface com.sap.aii.af.lib.mp.module.ModuleLocal loaded by sap.com/tokenEAR@com.sap.engine.boot.loader.ResourceMultiParentClassLoader@4328c55c@alive, but needed loader library:com.sap.aii.af.lib@com.sap.engine.boot.loader.ResourceMultiParentClassLoader@394642e5@alive)

     

    Many thanks in advance.

     

    Regards,

    Kumaran

  • Dear Martin,

     

    we got one business requirement where we need to call the JSON API in our ESS portal.

    we need to call the API using the authorization token generated run time dynamically.

     

    ADD API  https://benefits-qa.apigee.net/v1/members

    Update a member API end point in QA: https://benefits-qa.apigee.net/v1/members/{memberId}

    i am new to This topic.

    so kindly can u help me asap  with the complete solution step by step.

     

    Regards,

    Manjunath