reCAPTCHA integration on ServiceNow on Portal Forms - User bypasses captcha

Community Alums
Not applicable

The requirement:
My organization has a public Service Portal in which both users and external can submit form
. If the user is not logged in and submits the form, they want to Implement reCAPTCHA on these public portal forms.

On my PDI I have successfully created a widget that uses Google reCAPTCHA and provided it with the site and secret key. This is now working as seen in the screenshot. I next created a custom variable on a random Catalog Item and linked my previously created widget.

Question
:
Now on the portal form
, although the reCAPTCHA is there - the user can simply ignore and bypass the captcha and submit the form. How can I ensure that this is made mandatory, and prevents form submit unless successfully completed

Usajid_0-1699970539132.png

Usajid_1-1699970573153.png

 

Catpcha Widget Code:
Body HTML template

 

<script src='https://www.google.com/recaptcha/api.js'></script>
<body>
	<div class="g-recaptcha" data-sitekey="{{data.sitekey}}"></div>
	<input type="button" value="Confirm" ng-click="c.checkCap()" />
</body>

 

 

 

Client Controller:

 

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('You have not passed reCaptcha verification. \nPlease try again.');
			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!
}

 


Server Script

 

(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;
			}
	}
})();

 



1 ACCEPTED SOLUTION

Community Alums
Not applicable

Thanks for your help @mads2. However I managed to find the reason as to why the widget script was not updating the Catalog Item Form. It was actually an issue with my Widget Client Controller script not utilizing:  

 

 

api.controller = function($scope) {} 

 

 


For anyone else benefit in the future viewing this, with similar requirements; Implementing Google reCAPTCHA with ServiceNow Catalog Item Forms, below are the steps and code to follow:

1. Attain Google reCAPTCHA Site & Secret Key from:  https://www.google.com/recaptcha/admin/create
The domains should correspond to each instance URL of your ServiceNow instance. So they will be a separate keys for dev | uat | live instances.

 

usmansafk_1-1700560587333.png


2. Insert the keys into as properties in the sys_properties table on ServiceNow

 

3. Create a Google reCAPTCHA widget in ServiceNow.
Within the client controller you will reference a boolean/ checkbox/ yes-no field that will update depending on if captcha is true. 


Widget code:

 

Body HTML template

 

 

<script src='https://www.google.com/recaptcha/api.js'></script>
<body>
	<div class="g-recaptcha" data-sitekey="{{data.sitekey}}"></div>
	<input type="button" value="Confirm" ng-click="c.checkCap()" />
</body>

 

 

Server script

 

 

(function() {
	
	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; 
				data.captchaResponse = successResponse;
			} catch (ex) {
				gs.logError('Error in processing response from reCAPTCHA: ' + ex.message, 'CaptchaAjax script include');
				data.captchaResponse = false;
			}
	}
})();

 

 

Client controller

$scope.page.g_form.setValue('captcha_verification', 'Yes'); is the name of the field in the portal form stored within the Variable Set. Keep this naming consistent. 

 

 

api.controller = function($scope) {
    /* widget controller */
    var c = this;
    /**
     * c.checkCap should be called via ng-click, and will return a boolean value, 
     * indicating whether the user is validated and not a bot
     * If the user has performed no such validation, then an alert is shown
     */
    c.checkCap = function() {
        var resp;

        c.data.captchaSession = grecaptcha.getResponse();
        if (!c.data.captchaSession) {
            alert('You have not passed Captcha verification. \nPlease try again.');
            resp = false;
        }

        c.server.update().then(function() {
            var captchaResponse = c.data.captchaResponse;
            if (captchaResponse) {
                resp = confirmTrue(captchaResponse);
				$scope.page.g_form.setValue('captcha_verification', 'Yes');
            }
        
        });

        grecaptcha.reset();

        return resp;

    }

}

function confirmTrue(b) {
    return ((typeof b == 'string') ? (b.toLowerCase() == 'true') : (b == true));
}

 

 

 

4. Create a Variable Set in which the Captcha will be stored. 
Adding it to variable set will enable usability to any form in which Captcha is to be 
integrated.

Order: 10,000 (the make it generally at the bottom of most forms)

Variables:

line_break | Break Type field| (Only for cleanliness, as this way the captcha will be serrated from)
google_recaptcha | Custom with Label field | 'Please confirm the Captcha below to proceed.'
captcha_verification | Yes / No field | 'Captcha Verified?' (field will be hidden)

Catalog UI Polices:
a. Hide Google Captcha if Passed -
Simply, IF captcha_verification is YES, then HIDE google_recaptcha
Also, would suggest adding  an info message in the Runs script section:

 

 

g_form.addInfoMessage('Thank you for confirming Captcha.');

 

 

b. Hide 'Captcha Verified' field -

No conditions, just simply hide captcha_verification always


Catalog Client Scripts:

a. Prevent form submission on Captcha fail -

onSubmit client script to prevent user by passing the captcha

 

 

function onSubmit() {
	var captchaVerification = g_form.getValue('captcha_verification');

    if (captchaVerification != 'Yes') {
        g_form.addErrorMessage('You have not passed Captcha. Please try again');
        g_form.submitted = false;
        
        return false; //Abort the submission
    }
}

 

 

 

Final product:

usmansafk_2-1700563127026.png 

After the user presses confirm, the captcha disappears and user receives a message:

usmansafk_3-1700563226182.png

If the user attempts to ignore the captcha and submit the form, the user receives an error message:

usmansafk_4-1700563315918.png


_______________________________

That's all - I hope this helped. There was not much up to date resources in reCaptcha integration on ServiceNow community, YT etc., so i thought I would share this solution. Although this could certainly be refined, i.e. removal of the Confirm button (will leave you all with that one for yourselves ðŸ˜‰) - this certainly can serve as an MVP.

Please mark as helpful if this helped!

 

View solution in original post

9 REPLIES 9

mads2
Tera Contributor

A simple solution might be to create a hidden checkmark variable that will be set to true by the widget when the REST API returns a successful response.

 

Then you can create a onSubmit client script that evaluates if the checkmark is true and abort submit if it is not true.

 

I don't know how to modify the variable value from the widget but I guess it is possible in some way. 

mads2
Tera Contributor

You can set a hidden variable using $scope.page.g_form.setValue('variable_name',true);

Community Alums
Not applicable

Ok will try this today and reply.
If successful will make a post on community with the complete solution and code. It seems there has been people wanting implement this; reCAPTCHA integration on public forms, however there is not a comprehensive article or resource for it. 

Community Alums
Not applicable

"A simple solution might be to create a hidden checkmark variable that will be set to true by the widget when the REST API returns a successful response."

How to reference the widget from a client script?