sa
2019/08/25
 
25 Aug, 2019 | 3 min read

Securing WSO2 Enterprise Integrator Proxy Services Using OAuth 2.0

  • Krishni Andradi
  • Solutions Engineer - WSO2

WSO2 Enterprise Integrator

WSO2 Enterprise Integrator is a comprehensive integration solution that enables communication between various disparate applications. Instead of having your applications communicate with each other in all their different formats, they simply have to communicate with WSO2 Enterprise Integrator.

Proxy service

A proxy service is a virtual service that receives messages and optionally processes them before forwarding them to a service at a given endpoint. The proxy service allows you to perform necessary transformations and introduce additional functionality without changing your actual service.

WSO2 Enterprise Integrator supports creating proxy services. By default, it provides basic authentication for proxy services.

In this post, we look at authenticating these proxy services from OAuth 2.0.

I am using Okta Identity Provider as the OAuth 2.0 authentication provider.

Use case

Figure 1: Okta OAuth 2.0 authentication use case diagram

Generate token using Okta IDP

To develop the above use case, we should generate an Okta token. To do that, you must have a valid Okta account. If u dont have one, create a developer trial account.

  1. Create an OAuth application in Okta. I have created this application to implement code-based authentication. If you are using other methods, the following steps will change. Generating tokens for all methods are mentioned in Okta documentation.
  2. Generate authorization code.
  3. Generate access token.

Okta token verification

Okta has Okta token verification libraries to help us during the token verification process. As I am coding in Java, I am using the Java token verification library.

Next, I have to import these verification libraries to my verification mediator Java class and implement the verification process as instructed in Okta documentation.

Writing a custom mediator to verify the token

In order to write a custom java class mediator in WSO2 Enterprise Integrator, we have to write a class extending AbstractMediator class to write your token verification logic inside the mediate method.

import org.apache.synapse.mediators.AbstractMediator;
 
public class customClassMediator extends AbstractMediator {
    public boolean mediate(MessageContext mc) {
       // ............write your code here .......................
    }
}

First, create a Maven project and add the above class to the src folder. To extend AbstractMediator, you need to add the following dependency along with the repository to the pom.xml.


    
      WSO2 Public
      WSO2 Public
      https://maven.wso2.org/nexus/content/repositories/public/
    


    
	org.apache.synapse
	synapse-core
	2.1.7-wso2v115
    

In addition, we have to add Okta token verification libraries to the pom.xml.


	    com.okta.jwt
	    okta-jwt-verifier
	    0.4.0
	  
	
	    com.okta.jwt
	    okta-jwt-verifier-impl
	    0.4.0
	    runtime
  

Now, let's look at how to implement the token verification logic inside the mediator method.

First, we should extract the OAuth 2.0 Jwt token from the message context. To do that, we have to convert the Message context to Axis2 Message context. Using the Axis2 message context, we can parse request headers. We than have to perform some string parse operations to retrieve an access token. As shown below, getAccessTokenString and other methods retrieve access token from the message context.

private String getAccessTokenString(MessageContext msgCtx) {
		Map headers = (Map) ((Axis2MessageContext) msgCtx).getAxis2MessageContext().
                getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
		
		System.out.println(((Axis2MessageContext) msgCtx).getAxis2MessageContext().getAxisMessage());
		
        String apiKey = null;
        if (headers != null) {
            apiKey = extractCustomerKeyFromAuthHeader(headers);
        
            System.out.println(apiKey);
        }
       return apiKey;
	}
	
	private String extractCustomerKeyFromAuthHeader(Map headersMap) {
        //From 1.0.7 version of this component onwards remove the OAuth authorization header from
        // the message is configurable. So we dont need to remove headers at this point.
        String authHeader = (String) headersMap.get(HttpHeaders.AUTHORIZATION);
        if (authHeader == null) {
            return null;
        }
        if (authHeader.startsWith("OAuth ") || authHeader.startsWith("oauth ")) {
            authHeader = authHeader.substring(authHeader.indexOf("o"));
        }
        String[] headers = authHeader.split(OAUTH_HEADER_SPLITTER);
        if (headers != null) {
            for (String header : headers) {
                String[] elements = header.split(CONSUMER_KEY_SEGMENT_DELIMITER);
                if (elements != null && elements.length > 1) {
                    boolean isConsumerKeyHeaderAvailable = false;
                    for (String element : elements) {
                        if (!"".equals(element.trim())) {
                            if (CONSUMER_KEY_HEADER.equals(element.trim())) {
                                isConsumerKeyHeaderAvailable = true;
                            } else if (isConsumerKeyHeaderAvailable) {
                                return removeLeadingAndTrailing(element.trim());
                            }
                        }
                    }
                }
            }
        }
        return null;
    }
	
    private String removeLeadingAndTrailing(String base) {
        String result = base;
        if (base.startsWith("\"") || base.endsWith("\"")) {
            result = base.replace("\"", "");
        }
        return result.trim();
    }

Now, we have to verify the retrieved access token with the Okta IDP.

String jwtString = getAccessTokenString(synCtx);
try {
	 AccessTokenVerifier jwtVerifier = JwtVerifiers.accessTokenVerifierBuilder()
		                                    .setIssuer(issuerUrl)
		                                    .setAudience(audience)
		                                    .setConnectionTimeout(Duration.ofSeconds(1)) // defaults to 1000ms
		                                    .setReadTimeout(Duration.ofSeconds(1))       // defaults to 1000ms
		                                    .build();
	Jwt jwt= jwtVerifier.decode(jwtString);
	return true;
} catch (JwtVerificationException e) {
	e.printStackTrace();
	return false;
}

Finally, we have to build this Maven project and copy the generated jar file to the /lib folder. Add all third-party libraries we used to the /extensions folder.

After adding these folders, we need to start the integrator profile to make these changes effective.

Associating the custom mediator in the ESB proxy flow

WSO2 Integration Studio is an Eclipse-based drag and drop IDE designed for WSO2 Enterprise Integrator to develop ESB mediation flows, perform transformations, debug, etc. Open WSO2 Integration studio and create an ESB project. Then, create a SOAP proxy service in it.

In the input sequence in the proxy flow, drag and drop a class mediator to a execute token verification class. Click on the class mediator and edit properties to associate the custom class. When giving the class name, provide the full class name along with the package structure.

After the class mediator, drag and drop a call mediator and then add an endpoint you want to call.

Deploy this application as a car file to the server.

Run it!

To run this example, I have used the SOAP UI. First, create a SOAP project. If you have a WSDL, you can add it to SOAP UI when creating the SOAP project.

Add a request payload with input values and give the OAuth token as a header.

The header name is authorization. And value should be Bearer

Now, execute the flow. You will be able to view message flow by analyzing console logs.

Conclusion

In this example, we looked at how to add OAuth 2.0 authentication to an Enterprise Integrator proxy when using Okta as an IDP (Identity Provider). However, we can add OAuth 2.0 authentication to the EI proxy, regardless of what IDP is used. Only the mediation logic in the custom mediator class needs to be changed. For example, if you want to verify the token with WSO2 Identity Server, you have to replace your mediation logic with the following code.

public class customClassMediator extends AbstractMediator{
 
    private static final String CONSUMER_KEY_HEADER = "Bearer";
    private static final String OAUTH_HEADER_SPLITTER = ",";
    private static final String CONSUMER_KEY_SEGMENT_DELIMITER = " ";
    private static final String OAUTH_TOKEN_VALIDATOR_SERVICE = "oauth2TokenValidationService";
    private static final String IDP_LOGIN_USERNAME = "identityServerUserName";
    private static final String IDP_LOGIN_PASSWORD = "identityServerPw";
    private ConfigurationContext configContext;
    private static final Log log = LogFactory.getLog(SimpleOauthHandler.class);
 
    public boolean mediate(MessageContext msgCtx) {
        if (this.getConfigContext() == null) {
            log.error("Configuration Context is null");
            return false;
        }
        try{
            //Read parameters from axis2.xml
            String identityServerUrl =
                    msgCtx.getConfiguration().getAxisConfiguration().getParameter(
                            OAUTH_TOKEN_VALIDATOR_SERVICE).getValue().toString();
            String username =
                    msgCtx.getConfiguration().getAxisConfiguration().getParameter(
                            IDP_LOGIN_USERNAME).getValue().toString();
            String password =
                    msgCtx.getConfiguration().getAxisConfiguration().getParameter(
                            IDP_LOGIN_PASSWORD).getValue().toString();
            OAuth2TokenValidationServiceStub stub =
                    new OAuth2TokenValidationServiceStub(this.getConfigContext(), identityServerUrl);
            ServiceClient client = stub._getServiceClient();
            Options options = client.getOptions();
            HttpTransportProperties.Authenticator authenticator = new HttpTransportProperties.Authenticator();
            authenticator.setUsername(username);
            authenticator.setPassword(password);
            authenticator.setPreemptiveAuthentication(true);
            options.setProperty(HTTPConstants.AUTHENTICATE, authenticator);
            client.setOptions(options);
            OAuth2TokenValidationRequestDTO dto = this.createOAuthValidatorDTO(msgCtx);
            return stub.validate(dto).getValid();
        }catch(Exception e){
            log.error("Error occurred while processing the message", e);
            return false;
        }
    }
    private OAuth2TokenValidationRequestDTO createOAuthValidatorDTO(MessageContext msgCtx) {
        OAuth2TokenValidationRequestDTO dto = new OAuth2TokenValidationRequestDTO();
        Map headers = (Map) ((Axis2MessageContext) msgCtx).getAxis2MessageContext().
                getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
        String apiKey = null;
        if (headers != null) {
            apiKey = extractCustomerKeyFromAuthHeader(headers);
        }
        OAuth2TokenValidationRequestDTO_OAuth2AccessToken token =
                new OAuth2TokenValidationRequestDTO_OAuth2AccessToken();
        token.setTokenType("bearer");
        token.setIdentifier(apiKey);
        dto.setAccessToken(token);
        return dto;
    }
    private String extractCustomerKeyFromAuthHeader(Map headersMap) {
        //From 1.0.7 version of this component onwards remove the OAuth authorization header from
        // the message is configurable. So we dont need to remove headers at this point.
        String authHeader = (String) headersMap.get(HttpHeaders.AUTHORIZATION);
        if (authHeader == null) {
            return null;
        }
        if (authHeader.startsWith("OAuth ") || authHeader.startsWith("oauth ")) {
            authHeader = authHeader.substring(authHeader.indexOf("o"));
        }
        String[] headers = authHeader.split(OAUTH_HEADER_SPLITTER);
        if (headers != null) {
            for (String header : headers) {
                String[] elements = header.split(CONSUMER_KEY_SEGMENT_DELIMITER);
                if (elements != null && elements.length > 1) {
                    boolean isConsumerKeyHeaderAvailable = false;
                    for (String element : elements) {
                        if (!"".equals(element.trim())) {
                            if (CONSUMER_KEY_HEADER.equals(element.trim())) {
                                isConsumerKeyHeaderAvailable = true;
                            } else if (isConsumerKeyHeaderAvailable) {
                                return removeLeadingAndTrailing(element.trim());
                            }
                        }
                    }
                }
            }
        }
        return null;
    }
    private String removeLeadingAndTrailing(String base) {
        String result = base;
        if (base.startsWith("\"") || base.endsWith("\"")) {
            result = base.replace("\"", "");
        }
        return result.trim();
    }
   
}

In addition, If you want to protect a data service using OAuth 2.0 authentication, you can simply call your DSS API from the ESB flow.

For further information, please refer to the following code samples.

  1. Authentication: verify custom class maven project
  2. ESB proxy project
Undefined