Skip to Content

Intro

OAuth based authorization is a very hot topic nowadays, especially considering increased demand for integration with cloud based systems and services, where OAuth protocol is commonly used as one of de-facto standards for client authorization. Together with current limited out of the box support of this protocol in some standard HTTP based adapters (like REST adapter) and lack of such support in other adapters (like HTTP and OData adapters) that are shipped with SAP PI/PO, this drives developers to implement integration scenarios based on heavy involvement of 3rd party or custom developed components.

In SAP Community, there are already helpful materials that describe solutions when dealing with the requirement for OAuth authorization in receiver flows. Good recent examples are the blog written by David Halsband, where he provides detailed explanation and example of a solution for acquiring access token and setting corresponding dynamic configuration attribute that is further used in REST receiver communication channel as an additional required HTTP authorization header, from within message mapping using UDF, and the blog written by Stefan Zimara, where he explains how OAuth authorization flow can be supported using Java mapping program and setting corresponding dynamic configuration attribute by its means.

As an alternative to the approach described in blogs mentioned above, this blog is focused on achieving similar goal, but setting the dynamic configuration attribute using custom developed adapter module. Reasons for implementation of this logic in the adapter module and invoking it from the communication channel, as well as technical approach and realization details of a generic adapter module aiming setting dynamic configuration attributes in a dynamic way (hence, being acquired at runtime rather than specified at configuration time), are described in my earlier blog available for reading here. The custom adapter module AddDynamicConfigurationBean described in that blog, is used here as a basis for further development and provisioning of OAuth 2.0 based authorization support. Therefore, I will not repeat logic and implementation for setting dynamic configuration attributes from adapter module in this blog, but assume it is already taken care of and managed. In case you use different implementation logic for setting dynamic configuration attributes from custom adapter module, the provided standalone example can be adapted accordingly.

 

Prerequisite for further reading is understanding of general concepts and use cases of OAuth 2.0 – namely, difference between authentication protocol (like OpenID Connect) and authorization protocol (like OAuth), OAuth flows and involved parties (client, authorization server, resource server), possible grant types, concept of tokens (access and refresh) and their usage on client side when preparing requests to the OAuth protected resource.

 

For demonstration purposes, I provide sample implementation for acquiring access token from Microsoft Azure Active Directory authorization server using password grant type, the obtained access token is further used when sending requests to a Microsoft Azure based service. Depending on requirements, the same approach can be re-used and extended to support other grant types. Please note that in sake of simplicity, the provided demo does not implement token caching (for same token re-usability, which might be helpful for high volume scenarios to improve performance by reducing number of calls to the authorization server) and token refreshment (which might be considered for security purposes to avoid sending client secret such as password, in every access token request to the authorization server, but instead rely on capabilities of the authorization server in generating access token given valid refresh token for subsequent queries) – both mentioned features require implementation of saving of obtained JSON Web Token into secure persistence layer and mechanisms for its invalidation based on token’s expiry time. In contrast, in the demo, every new call of the adapter module leads to new access token request being generated from scratch and issued to the authorization server.

 

Although it is fine to manually construct HTTP request for access token (sent to the authorization server) and then handle HTTP response (from the authorization server) that will either contain access token or error if the token could not be provided, I will use specialized generic OAuth client library that provides additional level of abstraction and takes away necessity of managing lower levels (such as HTTP request/response handling). As a result, custom code becomes significantly more compact (as it will be seen further, it takes us very few code lines to implement required core logic) and much easier to maintain. In the demo, I use Apache Oltu (which is one of client- and server-side implementations of OAuth protocol) as an OAuth client library, but you might find alternative libraries and replace Oltu with similar library which fits you better. Moreover, some authorization server providers publish vendor specific libraries which were particularly designed and developed for use with certain authorization provider (for example, Microsoft provides Microsoft Azure Active Directory Authentication Library). Major reasons for me choosing Oltu are its small footprint, provider agnostic capabilities (the library can be used in a generic way with different authorization servers), templates of requests for several authorization servers (Google, LinkedIn, GitHub, etc.) together with possibility of constructing fine-grained custom-built access token requests targeted at generic authorization servers (in case provided templates are not relevant or acceptable). Note that Oltu client library utilizes JSON library for parsing JSON representation of OAuth token (JSON Web Token) and unmarshalling it into Java object. Thus, ensure both Oltu client library and JSON library are downloaded and plugged to the project when following this implementation approach – alternatively, if you use dependency management tools (like those provided by Maven or Gradle), check that automatic transitive dependency management is enabled unless you add JSON library as a dependency manually.

 

Preparation

First of all, it is necessary to familiarize yourself with specifics of access token request structure that is accepted by the queried authorization server – such as used authorization server URL, list and format of request parameters (for example, which additional parameters are required, shall they be passed as URL query parameters of the request or placed in the request body, etc.). Consult with documentation for the corresponding authorization server for this kind of details. Alternatively, it might be helpful to perform HTTP trace of communication between client and authorization server in already existing and working scenarios (not involving PI/PO) to reveal details of valid access token request format, structure and content.

Access token requests for password grant type which are currently accepted by Microsoft Azure Active Directory authorization server, follow commonly used principles and structure. An access token request is HTTP POST request sent via HTTP over SSL (HTTPS) to authorization server URL (which, for password grant type, is https://login.windows.net/common/oauth2/token) and containing request parameters in key-value pairs format in the request body (content type – HTTP header ‘Content-Type’ – of the request is ‘application/x-www-form-urlencoded’ and body contains either a map (form) of parameters key-value pairs, or a string containing keys and values delimited with ‘=’ and key-value pairs delimited with ‘&’). Request parameters are following:

  • ‘resource’ – resource for which access token is requested,
  • ‘client_id’ – corresponding client ID of the application registered in Microsoft Azure Active Directory,
  • ‘grant_type’ – grant type (for password grant type, ‘password’),
  • ‘username’ – service user name authorized for querying accessed resource,
  • ‘password’ – password of the service user,
  • ‘scope’ – scope of the access that is granted to the client application, here constant ‘openid’.

 

Implementation: standalone

Now we are ready to move on and implement logic to acquire access token. Goal at this step is to obtain a String object containing value of an access token – ultimately, this is what we need to progress further and make use of it when setting HTTP authorization header for the request to resource server. Code snippet below provides simplistic implementation that can be used even in standalone mode or placed in UDF / adapter module implementation in PI/PO. All sensitive input values were replaced with ‘<…>’.

String authorizationServerUrl = "https://login.windows.net/common/oauth2/token";
String resource = "<resource URL>";
String clientId = "<client ID>";
String username = "<user name>";
String password = "<password>";
String scope = "openid";
String accessToken = null;

try {

	OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());

	OAuthClientRequest oauthTokenRequest = OAuthClientRequest	.tokenLocation(authorizationServerUrl)
																.setGrantType(GrantType.PASSWORD)
																.setClientId(clientId)
																.setUsername(username)
																.setPassword(password)
																.setScope(scope)
																.setParameter("resource", resource)
																.buildBodyMessage();

	OAuthJSONAccessTokenResponse oauthTokenResponse = oAuthClient.accessToken(oauthTokenRequest,
			OAuth.HttpMethod.POST, OAuthJSONAccessTokenResponse.class);

	accessToken = oauthTokenResponse.getAccessToken();

} catch (OAuthSystemException e) {
	// Error handling logic
} catch (OAuthProblemException e) {
	// Error handling logic
}

As a result, in case access token was acquired successfully, String variable ‘accessToken’ will contain the obtained access token – prefixed with specification it is actually a bearer token (prefix ‘Bearer ‘ followed by acquired access token in the value of HTTP authorization header), it can be now assigned to HTTP authorization header when sending requests to resource server.

 

Pay attention to how Oltu client library abstracts operations with access token request and response by means of corresponding setter and getter methods. Please also note that this library comes with diverse variety of methods which can be utilized when preparing access token request (OAuthClientRequest) as well as unmarshalling and parsing response – online documentation accompanying the library contains more examples show casing its flexibility.

 

Implementation: embedded in custom adapter module

Finally, above code snippet can be wrapped into dynamic configuration provider class and embedded into custom adapter module following concept described in the earlier referred blog, so that it can be used to set required dynamic configuration attribute corresponding to adapter specific message attribute for the used adapter:

package com.doc.xpi.af.modules.dcappender.provider;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.oltu.oauth2.client.OAuthClient;
import org.apache.oltu.oauth2.client.URLConnectionClient;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse;
import org.apache.oltu.oauth2.common.OAuth;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.types.GrantType;

import com.arm.middleware.af.modules.dcappender.util.DynamicConfigurationAttribute;
import com.arm.middleware.af.modules.dcappender.util.DynamicConfigurationProvider;
import com.arm.middleware.af.modules.dcappender.util.DynamicConfigurationProviderException;
import com.sap.engine.interfaces.messaging.api.Message;

public class DynamicConfigurationProviderOAuthAzureAD implements DynamicConfigurationProvider {

	private static final String PARAMETER_DC_ATTR_NAMESPACE = "attribute.namespace";
	private static final String PARAMETER_DC_ATTR_NAME = "attribute.name";
	private static final String PARAMETER_AZURE_AD_URL = "oauth.azureADServiceURL";
	private static final String PARAMETER_OAUTH_RESOURCE = "oauth.resourse";
	private static final String PARAMETER_OAUTH_CLIENT_ID = "oauth.clientID";
	private static final String PARAMETER_OAUTH_GRANT_TYPE = "oauth.grantType";
	private static final String PARAMETER_OAUTH_USER = "oauth.user";
	private static final String PARAMETER_OAUTH_PASSWORD = "oauth.password";
	private static final String PARAMETER_OAUTH_SCOPE = "oauth.scope";

	private static final String OAUTH_GRANT_PASSWORD = "password";
	private static final String OAUTH_GRANT_CLIENT_CREDENTIALS = "client_credentials";
	private static final String OAUTH_GRANT_AUTHORIZATION_CODE = "authorization_code";
	private static final String OAUTH_GRANT_IMPLICIT = "implicit";

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

		List<DynamicConfigurationAttribute> dcAttributes = new ArrayList<DynamicConfigurationAttribute>();
		DynamicConfigurationAttribute dcAttribute = null;

		String oauthAccessToken = null;

		// Retrieve parameters
		String dcAttributeNamespace = parameters.get(PARAMETER_DC_ATTR_NAMESPACE);
		String dcAttributeName = parameters.get(PARAMETER_DC_ATTR_NAME);
		String dcAttributeValue = null;

		String authorizationServerUrl = parameters.get(PARAMETER_AZURE_AD_URL);
		String resource = parameters.get(PARAMETER_OAUTH_RESOURCE);
		String clientId = parameters.get(PARAMETER_OAUTH_CLIENT_ID);
		String username = parameters.get(PARAMETER_OAUTH_USER);
		String password = parameters.get(PARAMETER_OAUTH_PASSWORD);
		String scope = parameters.get(PARAMETER_OAUTH_SCOPE);
		String grant = parameters.get(PARAMETER_OAUTH_GRANT_TYPE);

		// Check mandatory parameters
		if (dcAttributeNamespace == null || dcAttributeNamespace.isEmpty() || dcAttributeName == null
				|| dcAttributeName.isEmpty()) {
			throw new DynamicConfigurationProviderException(
					"One or several required parameters are missing: dynamic attribute");
		}

		if (authorizationServerUrl == null || authorizationServerUrl.isEmpty() || resource == null || resource.isEmpty()
				|| clientId == null || clientId.isEmpty() || grant == null || grant.isEmpty() || scope == null
				|| scope.isEmpty()) {
			throw new DynamicConfigurationProviderException(
					"One or several required parameters are missing: Azure AD - OAuth access token request general parameters");
		}

		// Obtain access token from Microsoft Azure Active Directory
		if (grant.equals(OAUTH_GRANT_PASSWORD)) {
			// Authorization grant: resource owner password credentials

			if (username == null || username.isEmpty() || password == null || password.isEmpty()) {
				throw new DynamicConfigurationProviderException(
						"One or several required parameters are missing: Azure AD - OAuth access token request parameters for password authorization grant");
			}

			try {
				OAuthClient oauthClient = new OAuthClient(new URLConnectionClient());

				OAuthClientRequest oauthTokenRequest = OAuthClientRequest	.tokenLocation(authorizationServerUrl)
																			.setGrantType(GrantType.PASSWORD)
																			.setClientId(clientId)
																			.setUsername(username)
																			.setPassword(password)
																			.setScope(scope)
																			.setParameter("resource", resource)
																			.buildBodyMessage();

				OAuthJSONAccessTokenResponse oauthTokenResponse = oauthClient.accessToken(oauthTokenRequest,
						OAuth.HttpMethod.POST, OAuthJSONAccessTokenResponse.class);

				oauthAccessToken = oauthTokenResponse.getAccessToken();

			} catch (OAuthSystemException e) {
				throw new DynamicConfigurationProviderException(
						"Error when obtaining OAuth access token from Azure AD: " + e.getMessage(), e);

			} catch (OAuthProblemException e) {
				throw new DynamicConfigurationProviderException(
						"Error when obtaining OAuth access token from Azure AD: " + e.getMessage(), e);
			}

		} else if (grant.equals(OAUTH_GRANT_CLIENT_CREDENTIALS)) {
			// Authorization grant: client credentials
			// Reserved for future use
		} else if (grant.equals(OAUTH_GRANT_AUTHORIZATION_CODE)) {
			// Authorization grant: authorization code
			// Reserved for future use
		} else if (grant.equals(OAUTH_GRANT_IMPLICIT)) {
			// Authorization grant: implicit
			// Reserved for future use
		} else {
			// Reserved for future use
		}

		// Create dynamic configuration attribute, if access token was obtained
		if (oauthAccessToken == null || oauthAccessToken.isEmpty()) {
			throw new DynamicConfigurationProviderException("Error when obtaining OAuth access token from Azure AD");
		}

		dcAttributeValue = "Bearer " + oauthAccessToken;

		dcAttribute = new DynamicConfigurationAttribute(dcAttributeNamespace, dcAttributeName, dcAttributeValue);
		if (dcAttribute.isDynamicConfigurationAttributeComplete()) {
			dcAttributes.add(dcAttribute);
		}

		return dcAttributes;

	}
}

 

Configuration example

Currently HTTP and REST adapters support setting additional/custom HTTP headers by means of using respective adapter specific message attributes. Below is an example of corresponding configuration for HTTP receiver communication channel:

  • Enable use of additional HTTP header ‘Authorization’. Note that additional parameter ‘treatNSWithHTTP’ = ‘true’ was set – usage of this parameter is described in SAP Note 1958747 and is intended to provide support of namespace ‘http://sap.com/xi/XI/System/HTTP_AAE’ (instead of ‘http://sap.com/xi/XI/System’) for HTTP adapter specific message attributes (here, I use one of them – ‘HeaderFieldOne’):

  • Add the developed adapter module to the communication channel’s module processing sequence before call of the adapter application (for example, for HTTP adapter, before call of HttpAdapterBean), and provide required parameterization for it (which is translated to access token request parameters):

As a result, at runtime, during execution of the receiver communication channel, the developed adapter module is invoked and attempts to acquire access token. If access token is acquired successfully, an HTTP request sent to the resource server’s target endpoint configured in the receiver communication, is enriched with HTTP authorization header containing bearer token that conforms to OAuth 2.0 protocol specification.

 

Acknowledgements

  • Dorin Tufurin – for his expert knowledge in the field of Microsoft Azure technologies involved in the described example flow, provisioning help in regards to specifics of Microsoft Azure Active Directory OAuth authorization server and consultancy on how well-formed access token request shall look like to be accepted by the mentioned authorization server.
  • Eng Swee Yeoh and Vyacheslav Kuzyakin – for inspiring to look further in this topic, structure and finalize diverse findings in a blog.
To report this post you need to login first.

7 Comments

You must be Logged on to comment or reply to a post.

  1. Peter Chezowitch

    Thanks for this Blog!

    What I did not fully get: Can we by this method also fetch http header items from the response, such as xcsrf tokens?

    And will this approach work in both, Java 1.6 and Java 1.8?

    (0) 
    1. Vadim Klimov Post author

      Hi Peter,

      No, the described approach is relevant for setting HTTP header in the request, not getting/reading HTTP header from the response. If you need to read HTTP header of a response message, then feasibility of this will depend on adapter type you use. For example, REST adapter has such feature (see SAP Note 1873519, which also assumes you have appropriate SP level applied): HTTP headers of a response message become copied into corresponding message attributes (dynamic configuration attributes).

      The approach in general is JRE version agnostic and can be applied for both JRE 1.6 JRE 1.8. The library I used (Apache Oltu) works well in JRE 1.6 environments and higher. If you use other libraries (generic or vendor-specific), please check their documentation in regards to which versions of Java runtimes are supported. For example, Java version of the library I referred to (Microsoft Azure Active Directory Authentication Library), is currently compliant with JRE 1.7+, which means that library could not be used in PO 7.4 (JRE 1.6), which I used in the demo, but could only be used in PO 7.5 (JRE 1.8).

      Regards,

      Vadim

      (0) 
      1. Peter Chezowitch

        Hi Vadim,

        Thank you for your response!

        Regarding service note 1873519 it seems to be about the HTTP_AAE adapter. So I think with the REST adapter there is still no possiblity to fetch values from the HTTP response header, right?

        I had hoped that with an adapter module we might have the possiblity to fetch the HTTP response header somehow also for the REST adapter. In the scenarios I have been facing, we often had to get session information or tokens from the response header in order to authenticate in follow-up calls.

        So far I also had to revert to the HTTP_AAE adapter for this. And so far I have been lucky that no dynamic URLs where needed for the calls where I need to read the HTTP response header as the HTTP_AAE adapter doesnt support building dynamic URL paths.

        Nevertheless this blog is very interesting and I will keep it in mind if I come across a fitting scenario.

         

         

        (0) 
        1. Vadim Klimov Post author

          Hi Peter,

          You are correct, it was my typo – SAP Note 1873519 speaks about HTTP adapter, not REST adapter. As for REST adapter, I haven’t seen such feature implemented in it, yet.

          As I mentioned, the described module is only relevant for the request flow. For response flow, if it is required to handle response headers, adapter modules can be of help only it is technically possible to pass response headers to the message by the adapter application – but if the adapter application ignores them and omits, there is not much we can do during further message processing after the adapter application handed over the response message to Module Processor and correspondingly to adapter modules (in case of any).

          Regards,

          Vadim

          (0) 
  2. Sowjanya Bendapudi

    Hi Vadim,

    This question is not related to your post. I follow your posts and they are really good. I have a question regarding HTTP headers. I want to populate static or dynamic data on HTTPS header using proxy protocol. Tried couple of options like using interface IF_WSPROTOCOL , I am able to pass the data as part of SOAP header, now trying Service Groups. Please let me know if you have any suggestions.

    Regards,

    Sowjanya.

     

    (0) 
    1. Vadim Klimov Post author

      Hi Sowjanya,

      It is worth raising questions not related to the blog, in the Questions and Answers area – in this way, it is higher chance of getting the entire community involved and it will not confuse blog readers with comments not related to a blog topic.

      Regarding your question, why do you need to set custom HTTP headers in proxy protocol? Commonly, if you would need to pass custom header in proxy scenario, it shall be populated into SOAP header, not HTTP header.

      If you need to manipulate HTTP headers in the outbound call in ABAP, then this can be achieved using class CL_HTTP_CLIENT, but in that case, it will have nothing to do with proxies (will be a part of a generic HTTP client in ABAP scenario).

      Regards,

      Vadim

      (0) 
  3. Sowjanya Bendapudi

    Hi Vadim,

    My scenario is consuming any external service using proxy. We have an intermediate system which is using SOAP protocol. We are consuming from ABAP system.Requirement is to populate dynamic information via HTTP header. Currently I am using CL_PROXY_CLIENT.

    We have an end point created in SOAMANAGER, executing this end point using this class. In this scenario we tried using interface IF_WSPROTOCOL_WS_HEADER~SET_REQUEST_HEADER, this sets the data as SOAP header however we want the data as HTTP header in the format as KEY and Value similar to CL_HTTP_CLIENT->REQUEST->SET_HEADER_FIELD.

    Please let me know if you need any more details from me. B/w I did leave a question could not get any answers.

    Regards,

    Sowjanya.

     

    (0) 

Leave a Reply