Note: We've been pretty busy lately, finishing up the final draft of our book: Learning ServiceNow, but we wanted to share something we learned from a recent project, and a free application to automate this for you. Click here to jump to the app download!
CAPTCHA is an acronym (technically, a backronym) for "Completely Automated Public Turing-test to tell Computers and Humans Apart". Its' purpose is to (hopefully) quickly and (ideally) easily differentiate between actual human users, and computerized scripts or bots. The uses for this are vast and varied, but most organizations use them to ensure that the person submitting a given form is a person, not a bot.
There are a multitude of reasons that one might want to verify that the person submitting a form, is indeed a person, and not a robot. Perhaps you've got a public-facing CMS or portal page, or perhaps you just want to verify that no nefarious goings-on are scriptable if one of your users' accounts is compromised.
Whatever the reason, if you need to verify that a form submitter is indeed a person and not a robot (AKA: "CAPTCHA"), read on to find out how, and even get a downloadable update set that you can deploy into your instance to enable this functionality straight away (with a few minor setup-config tweaks).
First of all, we're going to be using Google's reCAPTCHA service because, it's arguably the easiest and least frustrating one for users to use, and because it's extraordinarily effective and doesn't require that users attempt to figure out what in the world the image to the right spells.
The first step to enabling this functionality, is to set up the reCAPTCHA service for your organization. To get started, head over to https://www.google.com/recaptcha. At the top-right, click on Get reCAPTCHA.
It is at this point that you may want to consider signing up for a company account that can be centrally managed. The last thing you want, is to have your company's CAPTCHA service tied with your account for all time. Once you've carefully considered that situation and have logged in, you'll be presented with a form like this:
You can re-use a CAPTCHA API on multiple pages, but you may, in the future, want to have more than one CAPTCHA on your CMS/Service portal/whatever, so fill in a good label. I'll just use something like "[company_name] ServiceNow CMS". In the Domains section, you could enter your full servicenow instance URI (your_company.service-now.com), but since you'd have to create a separate CAPTCHA for each of your instances (dev, QA, prod, etc), I recommend simply using the domain service-now.com. All sub-domains are automatically included.
Under Advanced Settings on the reCAPTCHA Key Settings form, you can move a slider toward higher security, or greater ease/simplicity of use. On this page, you'll also find some helpful information from Google: Your Site key, Secret key, a client-side script to include in your page, an HTML snippet, and some info on server-side configuration. Keep this page open for now.
CAPTCHA in a Service Portal Page
Note: We're only going to go over using reCAPTCHA in a service portal page, since it's the most complex implementation. However, these instructions can easily be adapted to work with a CMS/UI page, or even a UI macro. The server-side GlideAjax script included in the app at the bottom of this article could be called in virtually the exact same way from anywhere.
If you want to add the reCAPTCHA into a service portal page, you'll want to create a Widget to start with. This, you can later drop into your page wherever you like. Start by navigating to Service Portal -> Widgets in the application navigator, then click New to create a new widget record. Give the record a name, and an ID (something like reCAPTCHA will do fine for both).
Next, fill in the Body HTML template field. You'll want to include the script tag and the div from Step 1 on the Google reCAPTCHA page. I've also added a Test button and corresponding checkCap function to mine, in order to spit out the response from the CAPTCHA box, if the user attempted to verify their humanity with it, to make sure I'm getting a valid value. I would remove that before using this widget on any other page.
Here's what mine looks like:
<script src='https://www.google.com/recaptcha/api.js'></script> <script> function checkCap() { alert(grecaptcha.getResponse()); } </script> <body> <div class="g-recaptcha" data-sitekey="{{data.sitekey}}"></div> <input type="button" value="Test" onClick="checkCap()" /> </body>
Notice that I've replaced the site key from the code Google provided on the reCAPTCHA page, with "{{data.sitekey}}". I've done this because I want to be able to set the site key in a system property and retrieve it on load. This way, an admin can change this up on the fly. To set the value of data.sitekey, add the following line into the Server script field, below the comment lines but before the end of the function, then save the widget:
data.sitekey = gs.getProperty('recaptcha.site-key');
And then of course, you'll have to set a system property with the value of your site key (from the reCAPTCHA page) in the sys_properties table. This property should have the name "recaptcha.site-key".
Let's create that system property, as well as one called recaptcha.secret-key, with the value from the secret key that you were provided by the reCAPTCHA service, on the same page as the site-key.
To test this widget, navigate to Service Portal -> Pages in the application navigator, then create a new test page. Give it whatever name and ID you like, save the record, then click the Open in Designer Related link. In the Edit view, drag a Container onto the page, and then drag a 12 column box inside the container. In the Filter Widget search box at the top-left of the portal page designer, type "recaptcha" to bring up the widget you just created. Drag the widget into the 12-column box you just created, and then click the pop-out icon at the top-right of the portal page designer to open the page in a new tab. (If you have a popup blocker enabled, make sure you allow the popup.) On the new tab/window, you should see something like this:
Clicking the Test button will call the checkCap() function in the script in the body HTML template of our reCAPTCHA widget. This function calls the grecaptcha object class that's exposed from the api.js file that we pointed to, and runs the getResponse() method. This method returns a long alphanumeric string that corresponds to the "CAPTCHA session" of the user on the page. Once the user clicks the I'm not a robot button, this session key is generated. Until then, it's blank.
Clicking Test when the page first loads will throw an alert with a blank string, since the response generated from getResponse() is blank. However, if you click the I'm not a robot button and THEN click Test, you'll get an alert with a long and pseudo-random alphanumeric string. This is the "response" string. You can use this in the next step.
Note: You'll need to view this page by clicking "Try it" on the page record, or going to the URL /sp/?id=YOUR_PAGE_NAME on your instance. While viewing this page within the editor or "preview" mode, the script will not execute properly.
Shortly, we're going to have to make a few tweaks to our onClick script (including changing it to an "ng-click" script, since the service portal uses AngularJS), but you could write something very similar in an onSubmit catalog client script, or any onClick or onSubmit script for your CMS/UI page/UI macro. The following, is just how we'll accomplish this in the service portal, using a widget.
What we need to do now, is make it so when we click on the Test button, it checks whether you've successfully validated that you're not a robot. If we were to do this entirely client-side, anyone with any knowledge of scripting could fairly easily override whatever we attempted to do to validate it. As a general rule, anything "client-side" cannot be trusted when it comes to true security or validation, because in the end, the person (or bot) at the keyboard has ultimate control over their own client/browser. Instead, let's do a server-side lookup.
In the CMS (and most other cases), this can be done with a simple GlideAjax script. More info on GlideAjax here. However, since this is the Service Portal, we've got to do things the service portal way, so let's go back to that reCAPTCHA widget we created earlier, and make a few changes.
First, let's update the HTML Template to look like this:
<script src='https://www.google.com/recaptcha/api.js'></script> <body> <div class="g-recaptcha" data-sitekey="{{data.sitekey}}"></div> <input type="button" value="Test" ng-click="c.checkCap()" /> </body>
Now, the button is linked with an Angular client-side controller script, and the "ng-click" action is called when the button is clicked. In this case, that means calling the "checkCap()" function in the client controller. Here's what the client controller (AKA "client script") code looks like:
function() { /* widget controller */ var c = this; /** * c.checkCap should be called via ng-click, and will return a boolean value, * indicating whether the user is indeed validated to not be some sort of mechanized * button-pushing device. * If the user has performed no such validation, then an alert is shown, admonishing * them for attempting to take over the human race. */ c.checkCap = function() { var resp; c.data.captchaSession = grecaptcha.getResponse(); if (!c.data.captchaSession) { alert('Nice try, robot. \nYour mighty microchips are no match for our CAPTCHA technology!'); resp = false; } c.server.update().then(function() { var captchaResponse = c.data.captchaResponse; if (captchaResponse) { resp = isThisTrueOrWhat(captchaResponse); } }); grecaptcha.reset(); //Here, you might perform some action to allow or disallow submission based on the boolean value of the resp variable. return resp; } } function isThisTrueOrWhat(b) { return ((typeof b == 'string') ? (b.toLowerCase() == 'true') : (b == true)); //all this just to properly return a bool in JS. THERE'S GOT TO BE A BETTER WAY! }
This angular controller's scope is the widget, so on line 3: "var c = this;", just declares "c" as a shorthand for the widget's scope. Within that scope, we've defined the checkCap function (c.checkCap). This function first calls grecaptcha.getResponse(), which is an API provided by the inclusion of the reCAPTCHA script itself. This retrieves the current "session ID" for the user, and then validates that we were able to get a value from it. If not, we throw an alert: "nice try, robot", on line 15.
If we've got a valid response though, we call the Angular-ey c.server.update() API, and to provide our callback function, we chain the .then() method, passing in an anonymous inline function on lines 18-23. This inline function is called after our server script runs, so let's have a look at what that script looks like:
(function() { /* populate the 'data' object */ /* e.g., data.table = $sp.getValue('table'); */ data.sitekey = gs.getProperty('recaptcha.site-key'); if (input) { data.captchaResponse = null; data.captchaSession = input.captchaSession; try { var RESTCAPTCHA = new sn_ws.RESTMessageV2(); RESTCAPTCHA.setHttpMethod('post'); RESTCAPTCHA.setEndpoint('https://www.google.com/recaptcha/api/siteverify'); RESTCAPTCHA.setQueryParameter('secret', gs.getProperty('recaptcha.secret-key')); RESTCAPTCHA.setQueryParameter('response', data.captchaSession); var captchaResponse = RESTCAPTCHA.execute(); if (captchaResponse.haveError()) { gs.logError('Error in validating captcha response: ' + captchaResponse.getErrorMessage() + '. Status code: ' + captchaResponse.getStatusCode(), 'CaptchaAjax script include'); data.captchaResponse = false; } var successResponse = JSON.parse(captchaResponse.getBody()).success; //Relies on ES5 syntax. For ES3, use new JSON().decode(json_string); data.captchaResponse = successResponse; } catch (ex) { gs.logError('Error in processing response from reCAPTCHA: ' + ex.message, 'CaptchaAjax script include'); data.captchaResponse = false; } } })();
In this script, we first get data.sitekey, which is presented on-load to the form, and which the CAPTCHA script uses to retrieve the user's session ID.
On line 6 though, we check if input exists. If we are executing this function, then input does indeed exist. In this case, the input is what we set on line 13 of the client controller script (c.data.captchaSession = grecaptcha.getResponse();). Now in the scope of this server script, that data corresponds to input.captchaSession, which I've set back to data.captchaSession on line 8.
Next, on lines 10-21, I perform a REST call from the ServiceNow server, to Google's reCAPTCHA servers, to check if the user has been validated or not. If this throws an error, we log that error and set captchaResponse to false on lines 17 and 18. Otherwise, we set data.captchaResponse to the response we got from the reCAPTCHA servers. Though this function was performed asynchronously, we then pass the data object back to the client controller, which executes the chained .then() script we saw above, with the data object in-scope.
If that seemed a little bit complicated, a "big-picture" view might help. The overall execution goes like this:
- The page (and widget) are requested from the server
- The server looks up the system property "recaptcha.site-key", and sets the value to data.sitekey, because of our server script.
- The page (and widget) loads, and the variable data is available. It contains the sitekey property. The reCAPTCHA also loads, as does its' associated script and APIs (such as grecaptcha).
- The user clicks the "I am not a robot" button. This performs some validation, and sends some data about the user's behavior as well as the site key (from data.sitekey) to the reCAPTCHA server.
- The reCAPTCHA server response with the user's unique session ID - At this point, we don't know whether the user is a robot or not.
- The user clicks the Test button. This sets the c.data.captchaSession property to the value we got as a response from the CAPTCHA server. It then triggers the client-side c.checkCap() function.
- The c.checkCap() function uses c.server.update() to send a request to the server, which also sends along the data object (which contains the captchaSession property we just set) to the server.
- The server sees that there is input, so it runs the script which performs a REST API call to the CAPTCHA server, and checks if the user appears to be a human. It then sets that response to the property data.captchaResponse, which is then passed BACK to the client.
- The client receives the response from the server script, and runs the function in the .then() method, which checks the data.captchaResponse object property, and returns that response.
At that last step, rather than (or perhaps, just before) returning the response value, you might instead want to perform some action like submitting a form or doing whatever it is you want to do when the user validates that they aren't human, and clicks on a given button.
This would be a fair bit easier if you just wanted to use a GlideAjax script include and a UI Page, but alas, the Service Portal does not support GlideAjax.
Want to install the reCAPTCHA Integration app to your instance/Service Portal, but don't feel like manually re-typing all that code?
Want access to the GlideAjax script include that'll allow you to integrate reCAPTCHA with a UI Page or UI Macro?
We've got you covered! Just head over to the reCAPTCHA Integration app page, and click the download link!
SN Pro Tips is owned and written by The SN Guys, ServiceNow consultants. If your organization could benefit from our services, we humbly welcome you to schedule some time to chat and share your goals with us, so we can help your business Grow With Purpose.
- March 2024
-
February 2024
- Feb 12, 2024 5 Lessons About Programming From Richard Feynman
- July 2023
- May 2023
- April 2023
-
December 2022
- Dec 13, 2022 ServiceNow Developers: BE THE GUIDE!
- October 2022
-
August 2022
- Aug 23, 2022 Using .addJoinQuery() & How to Query Records with Attachments in ServiceNow
- Aug 18, 2022 Free, Simple URL Shortener for ServiceNow Nerds (snc.guru)
- Aug 16, 2022 How to Get and Parse ServiceNow Journal Entries as Strings/HTML
- Aug 14, 2022 New tool: Get Latest Version of ServiceNow Docs Page
- March 2022
- February 2022
- May 2021
- April 2021
- February 2021
-
November 2020
- Nov 17, 2020 SN Guys is now part of Jahnel Group!
- September 2020
- July 2020
-
January 2020
- Jan 20, 2020 Getting Help from the ServiceNow Community
- December 2019
- November 2019
-
April 2019
- Apr 21, 2019 Understanding Attachments in ServiceNow
- Apr 10, 2019 Using Custom Search Engines in Chrome to Quickly Navigate ServiceNow
- Apr 4, 2019 Set Catalog Variables from URL Params (Free tool)
- Apr 1, 2019 Outlook for Android Breaks Email Approvals (+Solution)
- March 2019
-
February 2019
- Feb 27, 2019 Making Update Sets Smarter - Free Tool
-
November 2018
- Nov 29, 2018 How to Learn ServiceNow
- Nov 6, 2018 ServiceNow & ITSM as a Career?
- October 2018
- September 2018
-
July 2018
- Jul 23, 2018 Admin Duty Separation with a Single Account
-
June 2018
- Jun 19, 2018 Improving Performance on Older Instances with Table Rotation
- Jun 4, 2018 New Free Tool: Login Link Generator
-
May 2018
- May 29, 2018 Learning ServiceNow: Second Edition!
- April 2018
- March 2018
-
February 2018
- Feb 11, 2018 We have a new book!
- November 2017
-
September 2017
- Sep 12, 2017 Handling TimeZones in ServiceNow (TimeZoneUtil)
- July 2017
-
June 2017
- Jun 25, 2017 What's New in ServiceNow: Jakarta (Pt. 1)
- Jun 4, 2017 Powerful Scripted Text Search in ServiceNow
- May 2017
- April 2017
-
March 2017
- Mar 12, 2017 reCAPTCHA in ServiceNow CMS/Service Portal
-
December 2016
- Dec 20, 2016 Pro Tip: Use updateMultiple() for Maximum Efficiency!
- Dec 2, 2016 We're Writing a Book!
-
November 2016
- Nov 10, 2016 Chrome Extension: Load in ServiceNow Frame
- September 2016
-
July 2016
- Jul 17, 2016 Granting Temporary Roles/Groups in ServiceNow
- Jul 15, 2016 Scripted REST APIs & Retrieving RITM Variables via SRAPI
-
May 2016
- May 17, 2016 What's New in Helsinki?
-
April 2016
- Apr 27, 2016 Customizing UI16 Through CSS and System Properties
- Apr 5, 2016 ServiceNow Versions: Express Vs. Enterprise
-
March 2016
- Mar 28, 2016 Update Set Collision Avoidance Tool: V2
- Mar 18, 2016 ServiceNow: What's New in Geneva & UI16 (Pt. 2)
-
February 2016
- Feb 22, 2016 Reference Field Auto-Complete Attributes
- Feb 6, 2016 GlideRecord & GlideAjax: Client-Side Vs. Server-Side
- Feb 1, 2016 Make Your Log Entries Easier to Find
-
January 2016
- Jan 29, 2016 A Better, One-Click Approval
- Jan 25, 2016 Quickly Move Changes Between Update Sets
- Jan 20, 2016 Customize the Reference Icon Pop-up
- Jan 7, 2016 ServiceNow: Geneva & UI16 - What's new
- Jan 4, 2016 Detect/Prevent Update Set Conflicts Before They Happen
-
December 2015
- Dec 28, 2015 SN101: Boolean logic and ServiceNow's Condition Builder
- Dec 17, 2015 Locate any record in any table, by sys_id in ServiceNow
- Dec 16, 2015 Detecting Duplicate Records with GlideAggregate
- Dec 11, 2015 Array.indexOf() not working in ServiceNow - Solution!
- Dec 2, 2015 Understanding Dynamic Filters & Checking a Record Against a Filter Using GlideFilter
- October 2015
-
August 2015
- Aug 27, 2015 Easily Clone One User's Access to Another User