fb
info@yenlo.com
WSO2 Identity Server 6 min

Impersonating User in Identity Server

sidharth
Sidharth Dash
Integration Specialist
identity

WSO2 Identity Server is a very strong CIAM tool that is used across industries and geographical locations. To serve and support end-users across geographical location about technical issues in any tech platform ‘language’ is a very big hurdle. But if the customer-service representative can see the ‘users’ perspective’ in the platform, it could help to avoid the language barrier, understand issue faster and thereby leading to a faster resolution time.

“User impersonation” is a mechanism through which customer support representatives can help end-users by impersonating them. This feature in not available by default in WSO2 Identity Server. With this requirement, I have developed and implemented a “custom grant” type to bridge the gap.

Some background information:

The stack consists of WSO2 APIM, IS-KM and a web-application. The web-application is secured by IS-KM and during login an access token is generated for end-user. The web-application uses API published in APIM to fetch end-user details and to access other services. A JWT token is generated in the GW which is passed on the BE, end-user details in the JWT heavily determines the response of the API. As the API is user sensitive the best way to impersonate the end-user is generating an access token on behalf his behalf.

Once this access token is generated then customer-service representative can access the web application the same way the end-user does. The GW will generate a JWT Token (with end-user details) which can be used to deliver personalized services by the web application.

High Level diagram:

highlevel diagram identity server

How it works:

As described earlier I have built a custom grant type and named it user_impersonation. This custom grant type user_impersonation which accepts access token of the customer-service representative and generates a new access token for a target user. The flow works on the following order:

  1. Customer-service representative will invoke the user_impersonation grant type.
    • Grant type validates if all the required params i.e. grant_type, target_user and access_token are provided.
    • Check if the customer-service representative access token is valid.
    • Check if the customer-service representative has proper role thereby its authorization.
    • Generate the access token for the end-user (target_user).

  2. Use the generate access token to invoke the API.
    • API-GW will validate the end-user (target_user) access token with Identity Server.
    • API-GW will generate the users JWT token for end-user (target_user) and pass to BE services.

  3. This JWT will used in the BE services to serve end-user (target_user) specific data like order status, product list, shopping cart etc. Now the customer-service representative has impersonated the end-user (target_user).

Sample postman call to APIM looks like:

werking

Configuration in Identity Server for custom grant type:

  • Add the below config at {IS-Home}/repository/conf/identity.xml 
example identity server code
  • Build the jar by `mvn clean install` and deploy .jar file in `{IS-Home}/repository/components/dropins/`
  • Sample code for the user_impersonation grant type is shown below.
package org.wso2.custom.grant;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.base.MultitenantConstants;
import org.wso2.carbon.identity.application.common.model.ApplicationPermission;
import org.wso2.carbon.identity.base.IdentityRuntimeException;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.identity.oauth.common.OAuthConstants;
import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception;
import org.wso2.carbon.identity.oauth2.dto.OAuth2TokenValidationRequestDTO;
import org.wso2.carbon.identity.oauth2.model.AccessTokenDO;
import org.wso2.carbon.identity.oauth2.model.RequestParameter;
import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext;
import org.wso2.carbon.identity.oauth2.token.handlers.grant.AbstractAuthorizationGrantHandler;
import org.wso2.carbon.identity.oauth2.util.OAuth2Util;
import org.wso2.carbon.identity.oauth2.validators.TokenValidationHandler;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.user.api.UserStoreManager;
import org.wso2.carbon.user.core.service.RealmService;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;

import java.util.ArrayList;
import java.util.Arrays;

public class MimickingGrantHandler extends AbstractAuthorizationGrantHandler {
    private static Log log = LogFactory.getLog(MimickingGrantHandler.class);
    public static final Logger audit_log = LoggerFactory.getLogger("AUDIT_LOG");

    @Override
    public boolean validateGrant(OAuthTokenReqMessageContext tokReqMsgCtx) throws IdentityOAuth2Exception {
        if(!super.validateGrant(tokReqMsgCtx)){
            return false;
        }
        boolean targetUserExist = false;
        boolean isBackUserAuthorized = false;
        RequestParameter [] oauth2RequestParameter=tokReqMsgCtx.getOauth2AccessTokenReqDTO().getRequestParameters();
        String accessToken="";
        String targetUser="";
        ArrayList<String> scopeList= new ArrayList<>();
        for (RequestParameter requestParam:oauth2RequestParameter) {
            String key = requestParam.getKey().trim();
            String value = requestParam.getValue()[0].trim();
            if(key.equalsIgnoreCase(MimickingGrantConstants.ACCESS_TOKEN)){
                accessToken=value;
            }
            if(key.equalsIgnoreCase(MimickingGrantConstants.TARGET_USER)){
                targetUser=value;
            }
            if(key.equalsIgnoreCase(MimickingGrantConstants.SCOPE)){
                scopeList.addAll(Arrays.asList(value.split(MimickingGrantConstants.SPACE)));
            }
        }

        boolean isValidToken=validateAccessToken(accessToken,MimickingGrantConstants.BEARER);
        AccessTokenDO accessTokenDO ;
        if(isValidToken){
            accessTokenDO= OAuth2Util.getAccessTokenDOfromTokenIdentifier(accessToken);
        }else {
            throw new IdentityOAuth2Exception("access token is invalid" );
        }
        if(log.isDebugEnabled()) {
            log.debug(" Access Token status : " +isValidToken +"\n"+
                    "Back office user : "+accessTokenDO.getAuthzUser().getUserName()+"\n"+
                    "Target_User = "+ targetUser +"\n");
        }

        /*
         * check if the target user exist and
         * if  user have valid token
         */
        targetUserExist=isTargetUserExist(targetUser);
        isBackUserAuthorized=isUserAuthorized(accessToken,getSPPermittedRoleList());


        if (isBackUserAuthorized && targetUserExist) {
            tokReqMsgCtx.setAuthorizedUser(OAuth2Util.getUserFromUserName(targetUser));
            scopeList.add(MimickingGrantConstants.USER_MIMICKING);
            String [] scope=scopeList.toArray(new String[scopeList.size()]);
            tokReqMsgCtx.setScope(scope);
        } else {
            if(!targetUserExist){
                throw new IdentityOAuth2Exception( targetUser +" doesn't exist" );
            }
            else {
                throw new IdentityOAuth2Exception("Authorization failed for " + accessTokenDO.getAuthzUser().getUserName());
            }

        }
        audit_log.info("Mimicking_Back-Office_User = "+accessTokenDO.getAuthzUser().getUserName() +"  Target_User = "+ targetUser +"  Status = Completed   User_Authenticated = "+ (isBackUserAuthorized && targetUserExist));
        return isBackUserAuthorized && targetUserExist;
    }

    private boolean validateAccessToken(String accessToken,String accessTokenType){
        try {
            TokenValidationHandler validationHandler = TokenValidationHandler.getInstance();
            OAuth2TokenValidationRequestDTO validationRequestDTO = new OAuth2TokenValidationRequestDTO();
            OAuth2TokenValidationRequestDTO.OAuth2AccessToken oAuth2AccessToken= validationRequestDTO.new OAuth2AccessToken();
            oAuth2AccessToken.setIdentifier(accessToken);
            oAuth2AccessToken.setTokenType(accessTokenType);
            validationRequestDTO.setAccessToken(oAuth2AccessToken);

            return validationHandler.validate(validationRequestDTO).isValid();
        } catch (IdentityOAuth2Exception e) {
            e.printStackTrace();
            log.error("error while validating access token");
        }
        return false;
    }
    /**
     *
     * Check if the impersonator user have the
     * @param accessToken
     * @return
     */
    private boolean isUserAuthorized(String accessToken,ArrayList <String> permittedRoleList){
        try {
            AccessTokenDO accessTokenDO= OAuth2Util.getAccessTokenDOfromTokenIdentifier(accessToken);
            OAuth2Util.getServiceProvider(accessTokenDO.getConsumerKey()).getSpProperties();
            String impersonatorUserName=accessTokenDO.getAuthzUser().getUserName();
            String tenantAwareUserName = MultitenantUtils.getTenantAwareUsername(impersonatorUserName);
            String userTenantDomain = MultitenantUtils.getTenantDomain(impersonatorUserName);
            String impersonatorCompleteUserName = tenantAwareUserName + "@" + userTenantDomain;
            int tenantId = IdentityTenantUtil.getTenantIdOfUser(impersonatorCompleteUserName);
            RealmService realmService = IdentityTenantUtil.getRealmService();
            UserStoreManager userStoreManager = realmService.getTenantUserRealm(tenantId).getUserStoreManager();
            String [] roles = userStoreManager.getRoleListOfUser(
                    MimickingGrantConstants.USER_STORE_DOMAIN+
                            MimickingGrantConstants.FORWARD_SLASH+
                            impersonatorUserName);
            ArrayList<String> rolesList = new ArrayList<String>(Arrays.asList(roles));
            rolesList.retainAll(permittedRoleList);
            if(rolesList.size()>=1){
                return true;
            }
        }  catch (UserStoreException | IdentityOAuth2Exception e) {
            log.error("error occurred while authorizing user "+e.getMessage());
        }
        return false;
    }

    private boolean isTargetUserExist(String targetUser) {
        String tenantAwareUserName = MultitenantUtils.getTenantAwareUsername(targetUser);
        String userTenantDomain = MultitenantUtils.getTenantDomain(targetUser);
        String targetUserCompleteUserName = tenantAwareUserName + "@" + userTenantDomain;
        int tenantId = MultitenantConstants.INVALID_TENANT_ID;
        try {
            tenantId = IdentityTenantUtil.getTenantIdOfUser(targetUserCompleteUserName);
        } catch (IdentityRuntimeException e) {
            log.error("Token request with Mimicking Grant Type for an invalid tenant : " +
                    MultitenantUtils.getTenantDomain(targetUserCompleteUserName));
            return false;
        }
        RealmService realmService = IdentityTenantUtil.getRealmService();
        try {
            UserStoreManager userStoreManager = realmService.getTenantUserRealm(tenantId).getUserStoreManager();
           return userStoreManager.isExistingUser(targetUser);
        } catch (UserStoreException e) {
            log.error("error occurred while searching the user "+ targetUser +" ; "+e.getMessage());
        }
        return false;
    }
    /**
     * Get permitted role list which is allowed to use this grant type
     * @return
     */
    private ArrayList getSPPermittedRoleList(){
        ArrayList <String> permittedRoleList =new ArrayList <String>();
        permittedRoleList.add(MimickingGrantConstants.PERMITTED_ROLES);
        return permittedRoleList;
    }
}

Conclusion:

With this approach we can successfully impersonate an end-user in WSO2 Identity server.

Yenlo is the leading, global, multi-technology integration specialist in the field of API-management, Integration technology and Identity Management. Known for our strong focus on best-of-breed hybrid and cloud-based iPaaS technologies. Yenlo is the product leader and multi-award winner in WSO2, Boomi, MuleSoft and Microsoft Azure technologies and offers best-of-breed solutions from multiple leading integration vendors.

With over 240+ experts in the API, integration, and Identity Access Management domain and over $35 million in annual revenue, Yenlo is one of the largest and best API-first and Cloud-first integration specialists worldwide.

Whitepaper:
The Identity and Access Management selection guide

iamco
Get it now
eng
Close
We appreciate it
Care to share

Please select one of the social media platforms below to share this pages content with the world