3 Replies Latest reply on Oct 5, 2017 12:40 PM by ttielebein RSS
    sclement Explorer

    Custom Authenticator Example

    Does anyone have a full example for creating a custom authenticator?  I have seen CS244163 and a few other responses to similar questions but the answers glance over the steps involved until it gets to authenticate.  For constructor and initializeEntity it just says to create them but no examples. 

      • Re: Custom Authenticator Example
        sclement Explorer

        After searching for an answer to this question I was able to piece together pieces from a number of different answers on the same subject.  Hopefully this answer will give a complete view of how to create your own Custom Authenticator.

         

        First create a new Thingworx Project in Eclipse and fill out the appropriate information (If you do not know how to do this part there are plenty of articles on setting up Eclipse to create Thingworx Extensions).  Next right click on the project you just created and choose New -> Authenticator.  You will have to answer some more information to complete this step.  The one item you may not be expecting is you  have to point it to a Tomcat Server.  I personally didn't have any luck pointing it to a remote server but my experience with Tomcat is very limited. I took the easy route of installing Tomcat on the machine I was developing the extension on.  It's important to note that the Tomcat instance doesn't really matter.  It is only used to add a few includes to the project, it just has to know where one is.  On the second screen you will want to un-check Requires Challenge and check Supports Session.

         

        You are now ready to start creating your Extension.  Start with the metadata.xml file under configfiles.  You can edit the source of the metadata.xml file and use the following as a template.  Be sure to change the ExtensionPackage name, FileResource file, Authenticator name, and Authenticator className to match your project.

         

        <?xml version="1.0" encoding="UTF-8" standalone="no"?>

        <Entities>

            <ExtensionPackages>

                <ExtensionPackage name="<your project name>"

                description="<a meaningful description for you>"

                vendor="<your company name>"

                packageVersion="1.0"

                minimumThingWorxVersion="7.2.1">

                    <JarResources>

                        <FileResource type="JAR"

                        description="<a meaningful description for you>"

                        file="<name of the jar file created for this extension.  Look in build-extension.xml to see this.  Usually <project name in all lowercase>.jar>" />

                    </JarResources>

                </ExtensionPackage>

            </ExtensionPackages>

            <Authenticators>

                <Authenticator name="<Name you want for your Authenticator in Thingworx>"

                className="<the class name you created when you added the Authenticator to your project>"

                description="<A meaningful description for you>"

                requiresChallenge="false"

                supportsSession="true"

                aspect.isEditableExtensionObject="true"

                enabled="false"

                priority="1" />

            </Authenticators>

        </Entities>

         

        The priority will determine the order all authenticators you have active in Thingworx are attempted in.  By default the Authenticators that come with Thingworx start at priority 100 and go up.  You will want to make yours lower than that.  If you add multiple Authenticators you may have to mess with this a bit but if this is your first one just go with any priority < 100.

         

        Next you will need to edit the java file for this extension.  If you look under the src directory it should be the only file in there and will be <your project name>.java.

         

            The file should have three default functions.  authenticate, issueAuthenticationChallenge, matchesAuthRequest.  Before we go into editing these I'll just quickly cover what they are for and the order they are attempted in.  matchesAuthRequest is first up.  This is where you can check the requestURI to determine if you want to attempt to authenticate for this request.  The request URI is not the exact same as the URL the user typed into get to the page.  It lops off the http://<servername>/Thingworx portion of the URL.  If you are setting up this authenticator to authenticate for a specific Mashup you can check to see if the URI contains the Mashup name.  If you are trying to take over authenticating for the Thingworx Composer or all Mashups you can see if the URI contains the word Composer or Mashups.  If the requestURI matches what you want in order to try authenticating then you return true.  Otherwise set this.setRequiresChallege(true); and throw new AuthenticatorException(requestURI);  By throwing the exception and passing the requestURI you can go to Monitorin -> Application in the Thingworx Composer and see exactly what the requestURI was if your Authenticator doesn't authenticate when you think it should.  If you always want to try to authenticate with your custom authenticator then don't do any checking here.  Just return true.

            The next step is to actually authenticate.  If you are using Single Sign On Authentication then issueAuthenticationChallenge will be called next.  Otherwise  authenticate will go next.  Both have the same purpose and flow however I will only be addressing authenticate because I am not using SSO so have no experience in it.  In authenticate you can do some further checking to determine if you should be authenticating or not.  Again, if you just want to go ahead and authenticate you don't have to do any checks.  Just go ahead with the authentication lines of code.  If you want to use a different account to authenticate with depending on how the Mashup or whatever you are trying to authenticate for is called you can check for parameters in the URL.  Then check the value of the parameter and authenticate (or don't) as appropriate.  If at anytime you determine you shouldn't be or can't authenticate then call this.setRequiresChallenge(true); and throw new AuthenticatorException(<some message>) and get out of the function.  This will log the reason you are dropping out of your authenticator in the Application log and allow Thingworx to ask the user for their credentials.

         

          OK now on to some example code.  This code will check to see if the user is attempting to run a specific mashup and if they are verify they sent the value we are looking for in the URL parameter called viewer.  As mentioned before I am not using SSO so we will not be using that function for authentication purposes.  You have the ability in your authenticator to check a username and password however this example won't be doing that.  What we'll do is assume if they send the correct value for viewer then we should log them in with a generic account (preferably one that only has rights to view the mashup or work inside Composer for a single project).  This will allow you to authenticate as a user that is not specified when on from the URL and you never have to use the accounts password including in your authentication code.  So for this code we'll assume the user is trying to go to a mashup by typing in the following url http://thingworx-server/Thingworx/Mashups/View_Values?viewer=let_me_see


        @Override

        public boolean matchesAuthRequest(HttpServletRequest httpRequest) throws AuthenticatorException {

          // TODO Auto-generated method stub

          String requestURI = httpRequest.getRequestURI();

          if(requestURI.contains("View_Values")) return true; // if you want to use this authenticator for multiple mashups just add else if lines for each Mashup

          else

          {

            this.setRequiresChallenge(true);

            throw new AuthenticatorException(requestURI);

          }

        }

         

        @Override

        public void issueAuthenticationChallenge(HttpServletRequest httpRequest, HttpServletResponse httpResponse)

        throws AuthenticatorException {

          // TODO Auto-generated method stub

          //Use for SSO

          this.setRequiresChallenge(true);

          throw new AuthenticatorException("we shouldn't be here"); // I'm not using single sign on but I want to know if we accidentally get here some how.

        }

         

        @Override

        public void authenticate(HttpServletRequest httpRequest, HttpServletResponse httpResponse)

        throws AuthenticatorException {

          // TODO Auto-generated method stub

          String parameter = httpRequest.getParameter("viewer"); // gets the value of our URL parameter viewer

          if(parameter.isEmpty()) // if the parameter was not supplied then drop out.

          {

            this.setRequiresChallenge(true);

            throw new AuthenticatorException("missing username parameter.  Go through normal challenge process");

          }

          else

          {

            switch(parameter) //This is preparing your authenticator to handle multiple Mashup authentications.  You can just use an if statement if you want

            {

            case "let_me_see":

              try{

                // validates the user name

                AuthenticationUtilities.validateEnabledThingworxUser("<a valid account with rights to the Mashup>");

                // This function does not check the password it just validates that the account supplied is enabled in Thingworx

                // Tells thingworx the user to run as

                this.setCredentials("<a valid account with rights to the Mashup>");

                // Tells Thingworx to proceed from a successful login attempt

                // This next line took me the longest to find and was missing from most examples.  It is needed to actually continue on to the page the

                // URL is trying to get to

                AuthenticationUtilities.getSecurityMonitorThing().fireSuccessfulLoginEvent("<a valid account with rights to the Mashup>", SharedConstants.EMPTY_STRING);

              }

              catch(Exception e){

                this.setRequiresChallenge(true);

                throw new AuthenticatorException("exception in case");

              }

              break;

              default:

                this.setRequiresChallenge(true);

                throw new AuthenticatorException("Viewer not defined. Going through normal authentication");

            }

          }

        }

         

        That should work for you.  You may need to add the following imports:

         

        import javax.servlet.http.HttpServletRequest;

        import javax.servlet.http.HttpServletResponse;

        import com.thingworx.common.SharedConstants;

        import com.thingworx.security.authentication.AuthenticationUtilities;

        import com.thingworx.security.authentication.AuthenticatorException;

        import com.thingworx.security.authentication.CustomAuthenticator;



        You should also add the following line right after public class <Class Name>

        private static final long serialVersionUID = 1L;


        Hopefully this helps.

        • Re: Custom Authenticator Example
          sclement Explorer

          Some last install / updating notes. 

           

          1.  When you import your Custom Authenticator extension it will be disabled by default.  Just go into Authenticators, find yours and enable it.

          2.  If you modify your extension and have to import it again as a new version you will most likely need to delete the existing extension, import the new one, enable it and restart Tomcat on your Thingworx server.  From my experience this is the only way to get the updates to take affect.  Otherwise Thingworx will show it imported the extension successfully but none of your changes will be in affect.

            • Re: Custom Authenticator Example
              ttielebein Collaborator

              For point 2, this is the way it works with all extensions. If you up the package number in the metadata.xml file, so that when you import it it is a newer version, like 1.1 instead of 1.0, then the updates should take effect immediately. Otherwise, you have to delete it and re-import it, as you said, or restart Tomcat after importing it. This is a great post, nice work!