Saturday, November 18, 2017

reCAPTCHA in PeopleSoft PIA Pages

In one of my previous blog posts, I showed how to implement reCAPTCHA 2.0 in PeopleSoft. My blog post only covered setting up the reCAPTCHA plugin on the PeopleSoft login page as a workaround for DOS, bot attacks, etc. I also mentioned that the effort to implement reCAPTCHA in a PeopleSoft PIA page (Classic/Fluid) should be very similar. But I found that there is one challenge with implementing the same client side code in the PIA (as noted by some comments in the post). The issue is a common problem we routinely run into when we combine a HTMLAREA (with JavaScript) and a PIA page! Every time there is an event that posts back to the server, the entire page is refreshed. Obviously and unfortunately for us, this will cause the HTMLAREA to reload as well. If there is javascript in the HTMLAREA and if we are referencing external scripts (as we do in reCAPTCHA), there is a risk that the script will be reloaded and any variables may be reset. In order to workaround this problem, we need to be extra careful with writing javascript in HTMLAREA making sure we properly 'manage' how our code is executed during such events. This is true for all cases not specific to this reCAPTCHA implementation.

To demonstrate the problem, I added the reCAPTCHA plugin (client side) code to a Classic PIA Page using a HTMLAREA.

Classic Page


HTMLAREA and Page Activate PeopleCode

The HTMLAREA is populated dynamically using Page Activate PeopleCode.


JavaScript and HTML for reCAPTCHA

The javascript and HTML needed to display the reCAPTCHA plugin is stored in a HTML object (CSK_RECAPTCHA).


Result

We can see that the reCAPTCHA plugin is displayed successfully on the page.


Demonstration of Problem

Basically, on the second postback the reCAPTCHA API javascript will no longer load and therefore result in the reCAPTCHA div to disappear. In the demo, we can see the problem occurs during the FieldChange (Server Trip) and the Save events.


Solution

To solve this problem, instead of directly using the script element in the HTMLAREA to reference the reCAPTCHA API javascript, I wrote a javascript function to load the script in the DOM.

Result


reCAPTCHA Callback Function

You may notice that there is another function called svRecaptchaCallback and it is used as the data-callback attribute value in the reCAPTCHA div element. This callback function is a great feature that is available with reCAPTCHA which allows us to execute our custom code upon a successful reCAPTCHA event. You can see in the following demo that the message is printed on the console once we complete a successful reCAPTCHA verification. As an example, this could be used to conditionally activate/display certain page field elements only after a successful reCAPTCHA verification. Please note that this is purely on the client side. That is, the callback function is available and executed on the browser as part of our HTMLAREA. This is not the same as the server-side validation!


Notes

- Environment Details: HCM 9.2 PUM Image 23, PeopleTools 8.56.01.
- The main focus of this post is the client-side implementation. The server side validation logic can be implemented based on the code provided in my previous post. Only difference here is that we will be executing the PeopleCode in a event such as SavePreChange or similar instead of the SignOn PeopleCode event.
- The implementation in this post is done as a proof of concept only. When implementing reCAPTCHA in a PIA page, we may also want to consider only prompting the user with the reCAPTCHA validation once. Prompting the users to confirm that they are not robots on every Save event might not result in a great user experience.

Sample Project on GitHub

https://github.com/SasankVemana/reCAPTCHA-in-PIA

8 comments:

  1. Could you please help in making me understand what really is happening with that readystate part or what it really does.. is it really should be incldued?

    if(oScript.readyState) {
    oScript.onreadystatechange=function() //for IE
    {
    if(oScript.readyState=="loaded"||oScript.readyState=="complete")
    {
    oScript.onreadystatechange=null;
    }
    };
    } else {
    oScript.onload=function()
    {
    //% console.log(' ' + sId + 'onload callback: ' + callback);
    if (typeof(callback) === 'function')
    {
    callback(callback);
    }

    };
    }

    ReplyDelete
  2. I assume that , it is used for executing the callback function, but i dont see the callback function being called in if statement.. is there anyother thing expected out of it ('oScript.onreadystatechange=null;')?

    Thank you.

    ReplyDelete
    Replies
    1. Hi Karthick - Good question. I borrowed this code from PT_CHART_LOAD delivered object that does a similar function of loading javascripts (refer loadScript function).

      I believe this is improvised from jQuery based on this information:
      https://stackoverflow.com/questions/1929742/can-script-readystate-be-trusted-to-detect-the-end-of-dynamic-script-loading
      https://github.com/jquery/jquery/blob/1.3.2/src/ajax.js#L264

      To answer your question, yest it is meant to take care of the callback function which is passed in as the third parameter to the function svLoadJS. I used a similar function in my javascript injection framework as well:
      https://pe0ples0ft.blogspot.com/p/javascript-injection-framework.html

      Although, I think my implementation is missing a line after the
      oScript.onreadystatechange=null;

      There should be a line:
      callback(callback);

      But it appears to work without any issue on IE 11, Firefox Quantum (57.0.1) and Chrome (62.0.3202.94).

      So, this part may have been necessary for IE in the previous versions for the callback to work:
      if(oScript.readyState=="loaded"||oScript.readyState=="complete")
      {
      oScript.onreadystatechange=null;
      }

      I hope this provides some clarity. Thanks!

      Delete
    2. Thanks Sasank!

      Delete
  3. Hi Sasank,
    Thanks for your blog. It is very much helpful. I am facing below issue. Can you please help me with this.

    Once I verify the Recaptcha and if any DB trip happens after verification , Recaptcha field is set to blank. Users need to verify captcha again. Did you face any similar issue? Thanks.



    ReplyDelete
    Replies
    1. I have not encountered this issue but I will try to replicate it and see if I have any ideas. Sorry for the delayed response.

      Delete
  4. Thanks for the blog.

    svLoadJS('https://www.google.com/recaptcha/api.js', 'CSK_RECAPTCHA', 'sCntr');

    what is 'sCntr' from above line.

    ReplyDelete
    Replies
    1. 'sCntr' is a dummy value that I send to the function which expects a callback function in that parameter. I don't need to execute anything in this case (as a callback), so I pass in a dummy value which will fail this condition if (typeof(callback) === 'function') because typeof 'sCntr' will be string.

      But if I wanted to execute a function say svTestCallBack (which I have defined somewhere), then I would replace the function call as follows:
      svLoadJS('https://www.google.com/recaptcha/api.js', 'CSK_RECAPTCHA', svTestCallBack);

      Delete