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

17 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
  5. Sasank - we've used some of your code to implement recaptcha on our online application. We are finding that if the propect fills everything out correctly, implements the recaptcha and it validates, they can move forward. But, if they have an issue, let's say with their email address, and then validate recaptcha, it goes thru but submission of the app is stopped due to the email issue. They fix that issue, then revalidate, and hit submit. Even though the recaptcha was done without error, we are getting this message: return vals { "success": false, "error-codes": [ "missing-input-response" ]} (99999,99999) Would you have any suggestions on where to start to look for fixing this?
    Thanks,
    Sharon

    ReplyDelete
    Replies
    1. We had the same issue. issue is in getting the grecaptcha response value from the POST request. i.e., at some point in your peoplecode you would have done 'request.getparameter(g-recaptcha-response)' to get the response id set by google. this id changes every time with the above approach, as we are inserting the script again and again in the same page. instead of inserting the script everytime in fieldchange, you have to use grecaptcha.reset() to reset the captcha after ajax response which happens after fieldchange.

      Delete
    2. Hi Anirudh,

      Thanks so much for sharing your experience. That makes perfect sense.

      Yes, the g-recaptcha-response will be different each time we reload the script.

      Where did you include the reset function call? Was that in CSK_RECAPTCHA javascript right after line 39 where we load the api.js script?

      Thanks,
      Sasank

      Delete
    3. Hi Sasank,
      No i added it in a separate html and called it every time after a fieldchange. Also, instead of using appendchild method to add the scrip to head, we directly added the script in the first html.

      Delete
  6. Hi Sasank,

    We are trying to implementing reCaptch in Campus Solutions admission page.
    When we add the code for HTML Area to show the reCaptcha portion there, entire pages load pretty fast except that reCaptcha image.

    Also your above example is for Peoplesoft signon page whereas ours' is the normal Fluid page.
    We are planning to place the call to the Check_Recatcha function call in the Submit button on our page once the user verifies the Captcha along with the login details.

    But we keep on getting the error as "Code could not be verified, please enter the correctly" from the reCaptcha.

    Do you have any idea what we might be missing?

    And why does the page takes too long to load the reCaptcha part.. And along with loading that it also reloads the whole content.

    ReplyDelete
    Replies
    1. First, there may be a slight delay in the load of the recaptcha DIV which is normal because it is referencing an external script. This is normal but it does not seem to be so noticeable that I would complain. You can see my demos in this post.

      This post actually talks about how we can add the reCAPTCHA to a PeopleSoft page (not signon page). The signon page example was my previous post where I discussed the end to end (client side and server side code). In this post, I only discussed the client side code because the server side code will be the same regardless of whether we add the reCAPTCHA div on the signon page or an authenticated PeopleSoft page.

      "Code could not be verified, please enter the correctly"
      This sounds like you have an issue with your public sitekey. You may want to verify if that is accurate.

      If you are on 8.56, you may want to review this post as well:
      https://pe0ples0ft.blogspot.com/2017/11/pt-8-56-htmlarea-respond-only-once.html

      Delete
    2. Hi Sasank,
      Actually I was referring both the blogs and added the comment here..
      Sorry for the confusion.

      Again with respect to the reCaptcha issue, initially the API key was the issue and due to that reCaptcha itself will not load.

      Once we paired the API keys and got the Data Key and Secret Key for the domain, we were able to see the reCaptcha..
      But it was taking around 20-25 secs to load the reCaptcha alone, my page would already be loaded by then and if user input1s something then due to reCaptcha load everything gets flushed and it's a blank page again.

      We also referred your issue of reCaptcha getting disappeared and we corrected the code as suggested in your blog.Even then the reCaptcha would disappear.

      The another issue was once I click the login/submit button. I was trying to perform the g-recaptcha-response to check the response. There was mention of grecaptcha.getResponse(opt_widget_id) but we are not sure what would be the widget id.

      Later we tried to go with the normal captcha itself. The problem with that is that we are able to get the captcha code generated but when I do the %Request.GetParameter() then the value is not fetched first time, but after I click Submit once and then enter the code it works.

      I'm clueless why it doesn't work the first time.
      I do have the below function alone inside an html1 object
      function SecurityCode() {
      var alpha = new Array('0','1','2','3','4','5','6','7','8','9');
      var i;
      for (i=0;i<6;i++){
      var a = alpha[Math.floor(Math.random() * alpha.length)];
      var b = alpha[Math.floor(Math.random() * alpha.length)];
      var c = alpha[Math.floor(Math.random() * alpha.length)];
      var d = alpha[Math.floor(Math.random() * alpha.length)];
      var e = alpha[Math.floor(Math.random() * alpha.length)];
      }
      var code = ' '+ a + ' '+ b + ' '+ c + ' '+ d + ' ' + e + ' ';
      document.getElementById('maincaptcha').value = code;
      }

      Later I use the AddJavaScript(html1.CaptchaCode) and AddOnLoadScript("SecurityCode()");

      After this I do have another html1 Object through which I paint the Generated Security Code on the Peolesoft fluid page inside an html1AREA.

      height: 30px;
      width: 143px;
      }
      .button_refresh {
      background: url(/cs/cs92dev/cache/AD_CAPTCHA_REFRESH.JPG);
      height:23px;
      width:23px;
      vertical-align:bottom;
      }








      Click for a new code








      I also tried removing the onload="SecurityCode()" from the body1 tag but nothing helps.

      When I use the %Request.GetParameter("maincaptcha") in Submit button, even though the html1AREA printed the Code on Peoplesoft page, it doesn't return value for the fist call of GetParameter, only the second time it works.

      Could you please guide where am I going wrong?

      Delete
    3. @avi - A 20-25 second delay is strange. I have not seen anything like that. You may want to check the browser console - network tab and see how long it is taking for the api script to load. This may be something on your network potentially. You may also want to post your issue on google recaptcha forums.

      Also, I have not used the normal captcha functionality (which I believe is the older captcha product with random numbers, etc.), so I don't have any ideas on how that works.

      Delete