Skip to main content

Oauth custom basic authenticator with WSO2 IS 5.1.0

WSO2 Identity Server supports Oauth2 authorization code grant type with basic authentication OOTB. But basic authentication is done only with WSO2 user store. So there could be use cases that basic authentication has to be done against some other system. In this case you follow below steps to achieve your requirement.
First you need to create an class which extends AbstractApplicationAuthenticator and implements LocalApplicationAuthenticator. Because this class is going to act as your application authenticator so it needs to be an implementation of application authenticator interface and to achieve this it needs to be a local authenticator as well. [2]
 public class CustomBasicAuthenticator extends AbstractApplicationAuthenticator implements LocalApplicationAuthenticator {  

Then you need to override the initiateAuthenticationRequest method so you can redirect to the page to enter user and password. In my sample I redirected to the page that is used by our default basic authenticator[1]. Code goes as follows.


      @Override  
      protected void initiateAuthenticationRequest(HttpServletRequest request,  
                             HttpServletResponse response,  
                             AuthenticationContext context)  
                throws AuthenticationFailedException {  
           //TODO: Implement custom redirecting to a custom login page if needed.  
           String loginPage = ConfigurationFacade.getInstance().getAuthenticationEndpointURL();  
           String queryParams =  
                     FrameworkUtils.getQueryStringWithFrameworkContextId(context.getQueryParams(),  
                                               context.getCallerSessionKey(),  
                                               context.getContextIdentifier());  
           try {  
                String retryParam = "";  
                if (context.isRetrying()) {  
                     retryParam = "&authFailure=true&authFailureMsg=login.fail.message";  
                }  
                response.sendRedirect(response.encodeRedirectURL(loginPage + ("?" + queryParams)) +  
                           "&authenticators=BasicAuthenticator:" + "LOCAL" + retryParam);  
           } catch (IOException e) {  
                throw new AuthenticationFailedException(e.getMessage(), e);  
           }  
      }  

Then you need to override processAuthenticationResponse method so you can do your own authentication. In my sample I didn’t do any authentication. You have to implement it to call your rest api. Then in your requirement you mentioned that you need to provision user to IS. So the code segment inside the if condition is to do that. In my sample I provisioned the user to the super tenant primary user store. You can decide on where to add user to.

      @Override  
      protected void processAuthenticationResponse(HttpServletRequest httpServletRequest,  
                                  HttpServletResponse httpServletResponse,  
                                  AuthenticationContext authenticationContext)  
                throws AuthenticationFailedException {  
           String username = httpServletRequest.getParameter(CustomBasicAuthenticatorConstants.USER_NAME);  
           String password = httpServletRequest.getParameter(CustomBasicAuthenticatorConstants.PASSWORD);  
           boolean isAuthenticated;  
           //TODO: Call the rest api to validate username and password  
           isAuthenticated = true;  
           //TODO: Here this is provisioned to the primary user store under super tenant. This can be changed to provision to the correct place.  
           if(isAuthenticated) {  
                UserStoreManager manager;  
                try {  
                     manager = CustomBasicAuthenticatorServiceComponent.getRealmService().getTenantUserRealm(-1234)  
                                             .getUserStoreManager();  
                } catch (UserStoreException e) {  
                     String msg = "Error while retrieving the user realm";  
                     LOGGER.error(msg, e);  
                     throw new AuthenticationFailedException(msg, e);  
                }  
                try {  
                     if (!manager.isExistingUser(username)) {  
                          manager.addUser(username, password, new String[0], new HashMap<String, String>(), null, false);  
                     }  
                } catch (UserStoreException e) {  
                     String msg = "Error while provisioning the user";  
                     LOGGER.error(msg, e);  
                     throw new AuthenticationFailedException(msg, e);  
                }  
                authenticationContext.setSubject(AuthenticatedUser.createLocalAuthenticatedUserFromSubjectIdentifier(username));  
           }  
      }  



And you need to implement the can handle method of the authenticator. In the sample I used the implementation same as basic authenticator.

      @Override  
      public boolean canHandle(HttpServletRequest httpServletRequest) {  
           String userName = httpServletRequest.getParameter(BasicAuthenticatorConstants.USER_NAME);  
           String password = httpServletRequest.getParameter(BasicAuthenticatorConstants.PASSWORD);  
           if (userName != null && password != null) {  
                return true;  
           }  
           return false;  
      }  


Then you need to make this bundle an osgi bundle and register the authenticator in osgi context. That’s done by service component class.


 /*  
  * 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.identity.application.authenticator.custom.basicauth.internal;  
 import org.wso2.identity.application.authenticator.custom.basicauth.CustomBasicAuthenticator;  
 import org.apache.commons.logging.Log;  
 import org.apache.commons.logging.LogFactory;  
 import org.osgi.service.component.ComponentContext;  
 import org.wso2.carbon.identity.application.authentication.framework.ApplicationAuthenticator;  
 import org.wso2.carbon.user.core.service.RealmService;  
 import java.util.Hashtable;  
 /**  
  * @scr.component name="application.authenticator.dbevaldev.component" immediate="true"  
  * @scr.reference name="realm.service"  
  * interface="org.wso2.carbon.user.core.service.RealmService"cardinality="1..1"  
  * policy="dynamic" bind="setRealmService" unbind="unsetRealmService"  
  */  
 public class CustomBasicAuthenticatorServiceComponent {  
   private static final Log LOGGER = LogFactory.getLog(CustomBasicAuthenticatorServiceComponent.class);  
      private static RealmService realmService;  
   protected void activate(ComponentContext context) {  
     try {  
       CustomBasicAuthenticator dbevaldevauthenticator = new CustomBasicAuthenticator();  
       Hashtable<String, String> props = new Hashtable<String, String>();  
       context.getBundleContext().registerService(ApplicationAuthenticator.class.getName(), dbevaldevauthenticator, props);  
       if (LOGGER.isDebugEnabled()) {  
         LOGGER.debug("Custom authenticator bundle is activated");  
       }  
     } catch (Exception e) {  
       LOGGER.fatal(" Error while activating custom authenticator ", e);  
     }  
   }  
   protected void deactivate(ComponentContext context) {  
     if (LOGGER.isDebugEnabled()) {  
       LOGGER.debug("Custom authenticator bundle is deactivated");  
     }  
   }  
      public static RealmService getRealmService() {  
           return realmService;  
      }  
      protected void setRealmService(RealmService realmService) {  
           if(LOGGER.isDebugEnabled()) {  
                LOGGER.debug("Setting the Realm Service");  
           }  
           CustomBasicAuthenticatorServiceComponent.realmService = realmService;  
      }  
      protected void unsetRealmService(RealmService realmService) {  
           if(LOGGER.isDebugEnabled()) {  
                LOGGER.debug("UnSetting the Realm Service");  
           }  
           CustomBasicAuthenticatorServiceComponent.realmService = null;  
      }  
 }  


Then you can build this with maven and copy the jar file to the following folder

$IS_HOME/repository/components/dropins

Then restart the Identity Server and browse to the carbon console. Then from the left menu Select Identity > Service Providers > List.


There you can select the service provider for the application and click on edit link to edit the particular service provider and expand the section Local & Outbound Authentication Configuration and there select Local authentication and select the newly added authenticator. And then select update.



Now you can trigger the authorization code grant type by invoking oauth2/authorize endpoint in IS. You can download the sample code from [3].

Comments

Popular posts from this blog

Generate JWT access tokens from WSO2 Identity Server

In Identity Server 5.2.0 we have created an interface to generate access tokens. Using that we have developed a sample to generate JWT tokens. You can find that sample under msf4j samples[1][2]. If you are build it as it is you will need to use Java 8 to build since msf4j is developed on Java 8. So you will need to run Identity Server on Java 8 as well. After building the project[2] please copy the jar inside target directory to $IS_HOME/repository/components/dropins/ directory. And then please add the following configuration to Identity.xml which is placed under $IS_HOME/repository/conf/identity/ folder inside tag OAuth . <IdentityOAuthTokenGenerator>com.wso2.jwt.token.builder.JWTAccessTokenBuilder</IdentityOAuthTokenGenerator> Then go to the database you used to store oauth tokens (This is the database pointed from the datasource you mentioned in the $IS_HOME/repository/conf/identity/identity.xml) and then alter the size of the column ACCESS_TOKEN of the tab

Integrate New Relic with WSO2 API Manager

In WSO2 API Manager, we have two transports. HTTP servlet transport and Passthru / NIO transport. All the web application requests are handled through HTTP servlet transport which is on 9763 port and 9443 port with ssl and here we are using tomcat inside WSO2 products. All the service requests are served via Passthru / NIO transport which is on 8082 and 8243 with ssl. When we integrate API Manager with new relic in the way discussed in blog posts [5],[6], new relic only detects the calls made to tomcat transports. So we couldn’t get the API calls related data OOTB. But by further analyzing new relic APIs I managed to find a workaround for this problem. New relic supports publishing custom events via their insights api[1]. So what we can do is publish these data via custom API handler[2]. Following is a sample implementation of a handler that I used to test the scenario. I will attach the full project herewith[7]. I have created an osgi bundle with this implementation so after building