This site contains the documentation that is relevant to older WSO2 product versions and offerings.
For the latest WSO2 documentation, go to https://wso2.com/documentation/.

Creating Custom Authenticators

In addition to local and federated authenticators, it is possible to create custom authenticators. The following is the API used to configure a custom authenticator.

/*
*  Copyright (c) 2005-2013, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
*  WSO2 Inc. licenses this file to you under the Apache License,
*  Version 2.0 (the "License"); you may not use this file except
*  in compliance with the License.
*  You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.wso2.carbon.identity.application.authentication.framework;

import java.io.Serializable;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext;
import org.wso2.carbon.identity.application.authentication.framework.exception.AuthenticationFailedException;
import org.wso2.carbon.identity.application.authentication.framework.exception.LogoutFailedException;
import org.wso2.carbon.identity.application.common.model.Property;

/**
 * API of the Application Authenticators.
 *
 */
public interface ApplicationAuthenticator extends Serializable {

	/**
	 * Check whether the authentication or logout request can be handled by the
	 * authenticator
	 * 
	 * @param request
	 * @return boolean
	 */
	public boolean canHandle(HttpServletRequest request);
	
	/**
	 * Process the authentication or logout request.
	 * 
	 * @param request
	 * @param response
	 * @param context
	 * @return the status of the flow
	 * @throws AuthenticationFailedException
	 * @throws LogoutFailedException
	 */
	public AuthenticatorFlowStatus process(HttpServletRequest request,
			HttpServletResponse response, AuthenticationContext context)
			throws AuthenticationFailedException, LogoutFailedException;
	
	/**
	 * Get the Context identifier sent with the request. This identifier is used
	 * to retrieve the state of the authentication/logout flow
	 * 
	 * @param request
	 * @return
	 */
	public String getContextIdentifier(HttpServletRequest request);
	
	/**
	 * Get the name of the Authenticator
	 * @return name
	 */
	public String getName();
	
	/**
	 * @return
	 */
	public String getFriendlyName();
	
    /**
     * Get the claim dialect URI if this authenticator receives claims in a standard dialect
     * and needs to be mapped to the Carbon dialect http://wso2.org/claims
     * @return boolean
     */
	public String getClaimDialectURI();
	
	/**
	 * @return
	 */
	public List<Property> getConfigurationProperties();
}

This API can be used to configure a custom authenticator. As an example, a Facebook authenticator is configured using the above API.

Configuring a custom authenticator for Facebook

See here for the SVN source code used to develop a sample custom Facebook authenticator.

/*
 * Copyright (c) WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 * 
 * WSO2 Inc. licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.wso2.carbon.identity.application.authenticator.facebook;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.amber.oauth2.client.request.OAuthClientRequest;
import org.apache.amber.oauth2.client.response.OAuthAuthzResponse;
import org.apache.amber.oauth2.common.exception.OAuthProblemException;
import org.apache.amber.oauth2.common.exception.OAuthSystemException;
import org.apache.amber.oauth2.common.utils.JSONUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jettison.json.JSONException;
import org.wso2.carbon.identity.application.authentication.framework.AbstractApplicationAuthenticator;
import org.wso2.carbon.identity.application.authentication.framework.FederatedApplicationAuthenticator;
import org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext;
import org.wso2.carbon.identity.application.authentication.framework.exception.AuthenticationFailedException;
import org.wso2.carbon.ui.CarbonUIUtil;

public class FacebookAuthenticator extends AbstractApplicationAuthenticator implements FederatedApplicationAuthenticator {

    private static final long serialVersionUID = 1L;
    private static final Log LOGGER = LogFactory.getLog(FacebookAuthenticator.class);

    @Override
    public boolean canHandle(HttpServletRequest request) {

        LOGGER.trace("Inside FacebookAuthenticator.canHandle()");

        // Check commonauth got an OIDC response
        if (request.getParameter(FacebookAuthenticatorConstants.OAUTH2_GRANT_TYPE_CODE) != null &&
            request.getParameter(FacebookAuthenticatorConstants.OAUTH2_PARAM_STATE) != null  &&
              FacebookAuthenticatorConstants.FACEBOOK_LOGIN_TYPE.equals(getLoginType(request))) {
            return true;
        }

        return false;
    }
    
    @Override
	protected void initiateAuthenticationRequest(HttpServletRequest request,
			HttpServletResponse response, AuthenticationContext context)
			throws AuthenticationFailedException {
		
		try {
            Map<String,String> authenticatorProperties = context.getAuthenticatorProperties();
            String clientId = authenticatorProperties.get(FacebookAuthenticatorConstants.CLIENT_ID);
            String authorizationEP = FacebookAuthenticatorConstants.FB_AUTHZ_URL;
            String scope = FacebookAuthenticatorConstants.SCOPE;

            String callbackurl = CarbonUIUtil.getAdminConsoleURL(request);
            callbackurl = callbackurl.replace("commonauth/carbon/", "commonauth");

            String state = context.getContextIdentifier() + "," + FacebookAuthenticatorConstants.FACEBOOK_LOGIN_TYPE;

            OAuthClientRequest authzRequest =
                                              OAuthClientRequest.authorizationLocation(authorizationEP)
                                                                .setClientId(clientId)
                                                                .setRedirectURI(callbackurl)
                                                                .setResponseType(FacebookAuthenticatorConstants.OAUTH2_GRANT_TYPE_CODE)
                                                                .setScope(scope).setState(state)
                                                                .buildQueryMessage();
            response.sendRedirect(authzRequest.getLocationUri());
        } catch (IOException e) {
            LOGGER.error("Exception while sending to the login page.", e);
            throw new AuthenticationFailedException(e.getMessage(), e);
        } catch (OAuthSystemException e) {
            LOGGER.error("Exception while building authorization code request.", e);
            throw new AuthenticationFailedException(e.getMessage(), e);
        }
        return;
	}

    private String getClientID(Map<String, String> authenticatorProperties, String clientId) {
        return authenticatorProperties.get(clientId);
    }

    @Override
	protected void processAuthenticationResponse(HttpServletRequest request,
			HttpServletResponse response, AuthenticationContext context)
			throws AuthenticationFailedException {
		
		 LOGGER.trace("Inside FacebookAuthenticator.authenticate()");
		
		try {
            Map<String,String> authenticatorProperties = context.getAuthenticatorProperties();
		    String clientId =  authenticatorProperties.get(FacebookAuthenticatorConstants.CLIENT_ID);
		    String clientSecret = authenticatorProperties.get(FacebookAuthenticatorConstants.CLIENT_SECRET);
		    String tokenEndPoint = FacebookAuthenticatorConstants.FB_TOKEN_URL;
		    String fbauthUserInfoUrl = FacebookAuthenticatorConstants.FB_USER_INFO_URL;
		
		    String callbackurl = CarbonUIUtil.getAdminConsoleURL(request);
		    callbackurl = callbackurl.replace("commonauth/carbon/", "commonauth");
		
		    String code = getAuthorizationCode(request);
		    String token = getToken(tokenEndPoint, clientId, clientSecret, callbackurl, code);
		    String authenticatedUser = getUserName(fbauthUserInfoUrl, token);
		    context.setSubject(authenticatedUser);
		} catch (AuthenticatorException e) {
		    LOGGER.error("Failed to process Facebook Connect response.", e);
		    throw new AuthenticationFailedException(e.getMessage(), e);
		}
	}

    private String getAuthorizationCode(HttpServletRequest request) throws AuthenticatorException {
        OAuthAuthzResponse authzResponse;
        try {
            authzResponse = OAuthAuthzResponse.oauthCodeAuthzResponse(request);
            return authzResponse.getCode();
        } catch (OAuthProblemException e) {
            throw new AuthenticatorException("Exception while reading authorization code.", e);
        }
    }

    private String getToken(String tokenEndPoint, String clientId, String clientSecret,
                            String callbackurl, String code) throws AuthenticatorException {
        OAuthClientRequest tokenRequest = null;

        String token = null;

        try {
            tokenRequest =
                           buidTokenRequest(tokenEndPoint, clientId, clientSecret, callbackurl,
                                            code);

            token = sendRequest(tokenRequest.getLocationUri());
            if (token.startsWith("{")) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Received token: " + token + " for code: " + code);

                }
                throw new AuthenticatorException("Received access token is invalid.");
            }
        } catch (MalformedURLException e) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("URL : " + tokenRequest.getLocationUri());
            }
            throw new AuthenticatorException(
                                             "MalformedURLException while sending access token request.",
                                             e);

        } catch (IOException e) {
            throw new AuthenticatorException("IOException while sending access token request.", e);
        }
        return token;
    }

    private OAuthClientRequest buidTokenRequest(String tokenEndPoint, String clientId,
                                                String clientSecret, String callbackurl, String code)
                                                                                                     throws AuthenticatorException {
        OAuthClientRequest tokenRequest = null;
        try {
            tokenRequest =
                           OAuthClientRequest.tokenLocation(tokenEndPoint).setClientId(clientId)
                                             .setClientSecret(clientSecret)
                                             .setRedirectURI(callbackurl).setCode(code)
                                             .buildQueryMessage();
        } catch (OAuthSystemException e) {
            throw new AuthenticatorException("Exception while building access token request.", e);
        }
        return tokenRequest;
    }

    private String getUserInformation(String fbauthUserInfoUrl, String token)
                                                                             throws AuthenticatorException {

        String userInfoString = null;
        try {
            userInfoString = sendRequest(fbauthUserInfoUrl + "?" + token);
        } catch (MalformedURLException e) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("URL : " + fbauthUserInfoUrl + token, e);
            }
            throw new AuthenticatorException(
                                             "MalformedURLException while sending user information request.",
                                             e);
        } catch (IOException e) {
            throw new AuthenticatorException(
                                             "IOException while sending sending user information request.",
                                             e);
        }
        return userInfoString;
    }

    private String getUserName(String fbauthUserInfoUrl, String token)
                                                                      throws AuthenticatorException {
        String userName = null;
        String userInfoString = getUserInformation(fbauthUserInfoUrl, token);
        try {
            Map<String, Object> jsonObject = JSONUtils.parseJSON(userInfoString);
            userName = (String) jsonObject.get(FacebookAuthenticatorConstants.USERNAME);
        } catch (JSONException e) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("UserInfoString : " + userInfoString, e);
            }
            throw new AuthenticatorException("Exception while parsing User Information.", e);
        }
        return userName;
    }

    @Override
    public String getContextIdentifier(HttpServletRequest request) {
        LOGGER.trace("Inside FacebookAuthenticator.getContextIdentifier()");
        String state = request.getParameter(FacebookAuthenticatorConstants.OAUTH2_PARAM_STATE);
        if (state != null) {
            return state.split(",")[0];
        } else {
            return null;
        }
    }
    
    private String sendRequest(String url) throws IOException {
        URLConnection urlConnection = new URL(url).openConnection();
        BufferedReader in =
                            new BufferedReader(
                                               new InputStreamReader(urlConnection.getInputStream()));
        StringBuilder b = new StringBuilder();
        String inputLine = in.readLine();
        while (inputLine != null) {
            b.append(inputLine).append("\n");
            inputLine = in.readLine();
        }
        in.close();
        return b.toString();
    }

    private String getLoginType(HttpServletRequest request) {
        String state = request.getParameter(FacebookAuthenticatorConstants.OAUTH2_PARAM_STATE);
        if (state != null) {
            return state.split(",")[1];
        } else {
            return null;
        }
    }

	@Override
	public String getFriendlyName() {
		return "facebook";
	}

	@Override
	public String getName() {
		return FacebookAuthenticatorConstants.AUTHENTICATOR_NAME;
	}
}