Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
vadimklimov
Active Contributor
Update on 22.07.2017

In SP07 for SAP NetWeaver 7.5, REST adapter has been enhanced and support of client credentials and resource owner password credentials grants for generic OAuth 2.0 flows has been released as a part of adapter standard functionality. Please refer to SAP Note 2405166 for more details on usage of this feature. We might expect further enhancement and feature extension of REST adapter, including more comprehensive support of OAuth 2.0 protocol, in further Support Packages, so stay tuned and follow product release notes. Though it shall be noted that some new features of REST adapter (such as mentioned extension in OAuth 2.0 support) are only available in release 7.5 and haven't been downported to lower releases (such as 7.4).

 

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.resource";
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.

14 Comments
Labels in this area