2014/02/11
11 Feb, 2014

Securing your Web Service with OAuth2 using WSO2 Identity Server

  • Bhathiya Jayasekara
  • Senior Technical Lead - WSO2

Introduction

Web applications sometime need access to certain user information in another web service. In such a case, how would you get your app authorized, on behalf of the user, against that web service? Years ago this problem was solved by users giving their credentials to the web application and then the web application uses them to authenticate itself against the web service. However, in the user’s perspective, giving away their credentials to another web application to log in as him/herself, is not a good story because, with user credentials, the web application gains full control of the user account until the user changes the password. People needed a solution, and they came up with a variety of solutions such as Google AuthSub, AOL OpenAuth, Yahoo BBAuth, Upcoming api, Flickr api, Amazon Web Services api [1] etc. But there were a lot of differences between each of them, and therefore, there was a need to standardize this. This is where OAuth came into play.

For more information on OAuth implementation watch our webinar on
or read our white paper on

What is OAuth?

OAuth is an open protocol that enables an application to access certain user information or resources from another web service, without giving the user’s credentials for the web service to the web application, e.g. a user needs to allow a third-party application to change his Twitter profile picture. When OAuth is used for authorization, it allows the third-party application to change the user’s profile picture after the user authorizes it to do so without giving credentials directly to the web application.

How does it work?

There are several Grant Types in OAuth 2. Some widely-used Grant Types are Authorization Code, Implicit, Client Credentials, Password, Refresh Token, etc. Depending on the Grant Type, there are different ways in which we can use OAuth for applications. We will be discussing about each of these types later in this article. In the following example, we use Authorization Code Grant Type.

Before Step 1, the Consumer App is registered with Identity Provider (IDP) and IDP issues a Client ID and a Client Secret for the client. In Step 1, the Consumer App sends the authorization request to IDP. That request contains Client ID, scope of authorization and callback URL. Here, the scope is used to specify for which level the Consumer App needs authorization. If we go back to the earlier example, the third-party application only needs authorization to change the user’s profile picture. So, we should not allow anything more than that for the Consumer App. This is what’s represented by ‘scope’ of the authorization. Callback URL is what’s used by IDP to contact the Consumer App back. Once the authorization request is granted (in Step 4), IDP contacts the Consumer App through this URL. In Step 2, IDP asks the user to authenticate himself and authorize the Consumer App for the given scope. In Step 3, the user, after authenticating himself first, reviews the authorization request’s scope and accepts it. In Step 4, IDP contacts the Consumer App through its callback URL and sends the authorization code. This authorization code, with Client Secret, can be used to obtain an Access Token to access the particular resource. That’s what happens in Step 5. In Step 6, IDP sends an Access Token to the Consumer App. In Step 7, the Consumer App uses that Access Token to request access to the particular resource from the resource server. In Step 8, the resource server contacts IDP to get the Access Token verified, and in Step 9, IDP sends the verification response back to the resource server. Thereafter, the resource server allows the Consumer App to access the resource under the given scope.

OAuth for your web service/application

In the example we discussed earlier, an identity provider is integrated with Twitter so that external application can access it on behalf of its users. Now, if you want to secure your web service using OAuth, how do you that? You need an identity provider for this purpose. WSO2 IS is such an identity provider that provides a simple and easy way to get this done in just a few steps.

Let’s discuss those steps using an example. In this example, we are going to secure a REST service using OAuth. The rest service used is YouTube search service. Here, WSO2 Enterprise Service Bus (WSO2 ESB) acts as the resource server.

Setting up the environment

In this example, IPs of host machines of each server is as follows.

WSO2 ESB 4.8.1 : 10.100.0.64
WSO2 IS 4.6.0 and Tomcat: 10.100.0.65

We will be using Playground2 webapp as the Consumer App. It’s using Apache Amber OAuth2 client to communicate with WSO2 IS, but you can use any OAuth client for your application. Playground2 web app, with its maven project, is attached at the end of this article. After downloading the war file, host it in the Tomcat server. Then we will be able to access it via https://10.100.0.65:8080/playground2.

Now let’s configure WSO2 ESB. Here we will be using an API element to configure REST service endpoint. We need to create a custom handler for the API element to achieve what we discussed in Steps 8 and 9. This handler will communicate with WSO2 IS and get the Access Token verified once the Consumer App sends the resource access request, with Access Token, to ESB.

Handler class is as follows. The complete maven project is attached at the end of the article. This handler reads the OAuth2TokenValidationService URL of WSO2 IS and admin credentials to access that service, from axis.xml of ESB. Then it calls this admin service and will pass the scope of the authorization with the Access Token. Then WSO2 IS will verify them and inform the ESB back about the verification status.

  

package org.wso2.handler;

import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.axis2.transport.http.HttpTransportProperties;
import org.apache.http.HttpHeaders;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.wso2.carbon.identity.oauth2.stub.OAuth2TokenValidationServiceStub;
import org.wso2.carbon.identity.oauth2.stub.dto.OAuth2TokenValidationRequestDTO;
import org.apache.synapse.ManagedLifecycle;
import org.apache.synapse.MessageContext;
import org.apache.synapse.core.SynapseEnvironment;
import org.apache.synapse.rest.AbstractHandler;
import org.wso2.carbon.identity.oauth2.stub.dto.OAuth2TokenValidationRequestDTO_OAuth2AccessToken;

import java.rmi.RemoteException;
import java.util.Map;

public class SimpleOauthHandler extends AbstractHandler implements ManagedLifecycle {

    private static final String securityHeader = HttpHeaders.AUTHORIZATION;
    private static final String consumerKeyHeaderSegment = "Bearer";
    private static final String oauthHeaderSplitter = ",";
    private static final String consumerKeySegmentDelimiter = " ";
    private static final String oauth2TokenValidationService = "oauth2TokenValidationService";
    private static final String identityServerUserName = "identityServerUserName";
    private static final String identityServerPw = "identityServerPw";
    private static final String BEARER_TOKEN_TYPE = "bearer";

    @Override
    public boolean handleRequest(MessageContext messageContext) {
        try {
            ConfigurationContext configCtx = ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, null);
            //Read parameters from axis2.xml
            String identityServerUrl = messageContext.getConfiguration().getAxisConfiguration().getParameter(oauth2TokenValidationService).getValue().toString();
            String username = messageContext.getConfiguration().getAxisConfiguration().getParameter(identityServerUserName).getValue().toString();
            String password = messageContext.getConfiguration().getAxisConfiguration().getParameter(identityServerPw).getValue().toString();

            OAuth2TokenValidationServiceStub stub = new OAuth2TokenValidationServiceStub(configCtx, 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);

            Map headers = (Map) ((Axis2MessageContext) messageContext).getAxis2MessageContext().
                    getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
            String apiKey = null;
            if (headers != null) {
                apiKey = extractCustomerKeyFromAuthHeader(headers);
            }

            OAuth2TokenValidationRequestDTO oauthReq = new OAuth2TokenValidationRequestDTO();
            OAuth2TokenValidationRequestDTO_OAuth2AccessToken accessToken =
                    new org.wso2.carbon.identity.oauth2.stub.dto.OAuth2TokenValidationRequestDTO_OAuth2AccessToken();
            accessToken.setTokenType(BEARER_TOKEN_TYPE);
            accessToken.setIdentifier(apiKey);
            oauthReq.setAccessToken(accessToken);
            try {
                return stub.validate(oauthReq).getValid();
            } catch (RemoteException e) {
                throw new Exception("Error while validating OAuth2 request", e);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public String extractCustomerKeyFromAuthHeader(Map headersMap) {
        String authHeader = (String) headersMap.get(securityHeader);
        if (authHeader == null) {
            return null;
        }

        if (authHeader.startsWith("OAuth ") || authHeader.startsWith("oauth ")) {
            authHeader = authHeader.substring(authHeader.indexOf("o"));
        }

        String[] headers = authHeader.split(oauthHeaderSplitter);
        if (headers != null) {
            for (int i = 0; i  1) {
                    int j = 0;
                    boolean isConsumerKeyHeaderAvailable = false;
                    for (String element : elements) {
                        if (!"".equals(element.trim())) {
                            if (consumerKeyHeaderSegment.equals(elements[j].trim())) {
                                isConsumerKeyHeaderAvailable = true;
                            } else if (isConsumerKeyHeaderAvailable) {
                                return removeLeadingAndTrailing(elements[j].trim());
                            }
                        }
                        j++;
                    }
                }
            }
        }
        return null;
    }

    private String removeLeadingAndTrailing(String base) {
        String result = base;

        if (base.startsWith("\"") || base.endsWith("\"")) {
            result = base.replace("\"", "");
        }
        return result.trim();
    }

    @Override
    public boolean handleResponse(MessageContext messageContext) {
        return true;
    }

    @Override
    public void init(SynapseEnvironment synapseEnvironment) {
        //ignore
    }

    @Override
    public void destroy() {
        //ignore
    }
}

Then build the handler project ($ mvn clean install) and get the handler.jar created. Thereafter, put it in $ESB_HOME/repository/components/lib.

Add the following configs to $ESB_HOME/repository/conf/axis2/axis2.xml.

  


https://10.100.0.65:9443/services/OAuth2TokenValidationService

admin
admin

Restart ESB and go to Manage > Service Bus > Source View

Add the following API element config.

  

      
         <inSequence>
            <header name="Authorization" scope="transport" action="remove"/>
            
               
                  <address uri="https://gdata.youtube.com/feeds/api/videos?q=wso2"/>
               
            
         </inSequence>
      
      
         <handler class="org.wso2.handler.SimpleOauthHandler"/>
      
   

In this API element, we configure backend REST service, which needs to be secured with OAuth and the handler class we have implemented. In this example, we have to remove the ‘Authorization’ header of the incoming message, which is used to authenticate the service exposed by ESB from message before sending it out to the backend service (unless YouTube tries to validate this token and gives an error message saying ‘Invalid Token’).

Now let’s configure WSO2 IS.

First, let’s register this Consumer App in WSO2 IS. Download and start WSO2 IS. Once logged in, go to Main > Manage > OAuth and click on Register New Application.

For this example, we are using OAuth version 2. Give any name to the application. The callback URL of our application is https://localhost:8080/playground2/oauth2client. There are multiple grant types supported by WSO2 IS. We will be discussing them individually, later in this article.

Once the app is added, it will be listed as follows.

Now Click on the application name and the following page will come up.

When the app has been added, a Client ID and a Client Secret are generated for the application. Consumer Application should have them with it. Client ID is public where Client Secret is a secret that should not be exposed to public. Consumer app should also know Authentication and Access Token endpoints of IDP (i.e. WSO2 IS in this case).

Go to https://l10.100.0.65:8080/playground2 and click on the search image.

In this example, we will be using ‘Authorization Code’ Grant Type. Now we can give Client ID and authorization endpoint of IDP to the Consumer App. Here, we are sending our initial request (Step 1) to IDP’s authorization endpoint.

Then IDP (WSO2 IS) shows the following page to the user.

Once we click Continue, it will ask to authenticate the user (Step 2).

Once logged in, it will ask you to review and authorize the Consumer App’s authorization request. Then we approve the request (Step 3).

Once we approved the request, the Consumer App get’s the authorization code (Step 4).

Now the Consumer App can request for the Access Token. In this request, it needs to specify Authorization Code and Client Secret. This request is sent to the Access Token endpoint of the IDP (Step 5).

Then the IDP will send an Access Token (Step 6). Now the Consumer App can send the request to ESB with the Access Token (Step 7). In this example, we call ESB’s ‘YouTubeSearch’ service, which we created earlier. That service eventually calls YouTube Search service.

Corresponding curl command for this is like this.

  
curl -v -X GET -H “Authorization: Bearer <ACCESS_TOKEN>” 
https://10.100.0.64:8280/search 

Once this request hits the ESB, the handler we deployed will call IDP (i.e. WSO2 IS) and get the Access Token verified (Steps 8 and 9). Then the ESB will call backend REST service and get response back to the Consumer App (Steps 10, 11 and 12).

Grant types supported by WSO2 IS

Authorization code

This is the type we discussed throughout the article, where IDP issues an Authorization code once the Consumer App’s authorization request is approved by the user.

Implicit

In this type, the client secret is not involved. This is mostly used for mobile apps and web browser-based apps (javascript apps, etc.) where the client secret cannot be kept in secret. In this method, once the user authorizes the Consumer App’s authorization request, the app gets the Access Token directly.

Password

In this type, the user’s credentials are sent with initial request. This seems to contradict with the purpose of having OAuth, which is avoiding giving away your password to a third-party application. But actually, it doesn’t, because this method is supposed to be used by the applications that are owned by the resource server itself, and not any other third party.

Client credentials

Resource owner (i.e. user) is not involved in this method. Here, the Consumer App uses its Client ID and Client Secret to get an Access Token. This method is supposed to be used when the app needs to access its own data rather than the user’s protected data.

Refresh token

In this method, IDP provides a Refresh Token (with Access Token), which the Consumer App can use to get a new Access Token once the current Access Token is expired. So the user does not have to get involved to authorize every time the Access Token expires.

SAML

In this grant type, Consumer application can present an SAML assertion to IDP, and get an Access Token, without requiring the user to authenticate again. This is somewhat similar to the Refresh Token type.

Conclusion

You may want to allow third-party apps to access your web service to do particular tasks on behalf of users. So, apps need a way to authenticate themselves against your web service. Asking your users to simply give their passwords to third-party apps is not a solution, because it allows those apps to do anything that user can do, regardless of what the user really wants the app to do on their behalf. In such a situation, OAuth is a good solution that does not compromise the user account’s security, because in OAuth the user does not have to divulge credentials to third-party apps. To secure your web service with OAuth, you don’t have to implement it yourself from the scratch. WSO2 IS is an identity provider that does just that for you with a few simple steps. Once you have configured your web service with WSO2 ESB, third-party applications only have to register themselves in WSO2 IS, and you are ready to market.

Resources

Playground2.war/ Source

Handler-1.0.0.jar/ Source

References

[1] https://oauth.net/about/

[2] https://oauth.net/2/

[3] https://docs.wso2.org/display/ESB481/Securing+REST+APIs

[4] https://docs.wso2.org/display/ESB481/Getting+Started+with+REST+APIs#GettingStartedwithRESTAPIs-addAPIs

[5] https://docs.wso2.org/display/IS460/OAuth+2.0+Playground+with+WSO2+Identity+Server

[6] https://malalanayake.wordpress.com/2013/04/05/apply-oauth2-0-base-security-for-rest-endpoint-with-wso2esb-4-6-0-and-wso2is-4-1-1-alpha/

[7] https://aaronparecki.com/articles/2012/07/29/1/oauth2-simplified

IS

 

About Author

  • Bhathiya Jayasekara
  • Senior Technical Lead
  • WSO2