Introducing Google reCAPTCHA in Einstein Bots

Einstein for Service is a powerful set of features to support your agents and satisfy your customers. Using Einstein Bots can greatly reduce the time efforts made to resolve the customers queries. Since Bots are the virtual agents acting towards the customer complaints or grievances, they are trained in a manner to listen to customer's queries and act on some pre-defined rules. If you want to know more on how to use/create Einstein Bots and Chat deployment, visit the Einstein for Service module of Salesforce help documentation or Einstein Bots Developer Cookbook from Salesforce developer documentation.

Einstein Bots are great to minimize the human efforts but at the same time they may also be cause for hack to the application with DDoS(Distributed Denial of Service) and/or Attach Vectors, we have to adopt some sort of authentication mechanism to regularize the application access. Google reCAPTCHA can help in identifying if the requests are coming from human or not. This blog will help integrate Google reCAPTCHA v2 successfully in our Experience Builder and in Einstein Bots henceforth.

Implementation:

Enabling Google reCAPTCHA in our Einstein Bots will require following steps to be followed:
  1. Enable the Google reCAPTCHA scripts in Experience Builder.
  2. Create a custom Pre-Chat Form with Google reCAPTCHA.
  3. Add the Embedded Service Chat in our Experience Builder.

Now let's focus on step by step process.

Enable the Google reCAPTCHA scripts in Experience Builder

To enable the Google reCAPTCHA in Experience Builder, follow the below steps

  1. Setup Google Admin console for using reCAPTCHA: First step is to set up the site key and secret key in order to use Google reCAPTCHA. Follow the steps as given hereCopy the site key and we will use this in our next step.
  2. Login to Sandbox/Production org.
  3. Go to Digital Experiences → All Sites and open the Builder for next to Site URL.
  4. Click on the Gear Icon(⚙)  Advanced  Edit Head Markup

  5. Add the following script in the Head Markup and Save. See the CustomEvents create to render the Google reCAPTCHA and track the response back. Make sure you replace the ENTER_YOUR_SITEKEY with the Site Key created in step 1
    <!--reCaptcha v2 Checkbox-->
    <script>
        var prefix;
        var verifyCallback = function(token) {
            var message = prefix + 'success';
            window.parent.document.dispatchEvent(new CustomEvent('grecaptchaVerified', {detail: {response: token, message: message}}));
        };
        var expireCallback = function() {
            var message = prefix + 'expire';
            window.parent.document.dispatchEvent(new CustomEvent('grecaptchaExpired', {detail: {message: message}}));
        };
        var errorCallback = function() {
            var message = prefix + 'error';
            window.parent.document.dispatchEvent(new CustomEvent('grecaptchaError', {detail: {message: message}}));
        };
        document.addEventListener('grecaptchaRender', function(e) {
            prefix = e.detail.prefix;
            prefix = prefix ? prefix : '';
            grecaptcha.render(e.detail.element, {
                'sitekey': 'ENTER_YOUR_SITEKEY',
                'callback': verifyCallback,
                'expired-callback': expireCallback,
                'error-callback': errorCallback
            });   
        });
        document.addEventListener('grecaptchaReset', function() {
            grecaptcha.reset();
        }); 
    </script>
    <script src='https://www.google.com/recaptcha/api.js?render=explicit' async defer></script>
  6. Click on the Gear Icon(⚙)  Advanced  Security & Privacy
    1. Clickjack Protection Level: Allow framing by the same origin only (Recommended)
    2. Security Level: Relaxed CSP: Permit Access to Inline Scripts and Allowed Hosts
    3. Add following Trusted Sites for Scripts
      • Google: https://www.google.com
      • Gstatic: https://www.gstatic.com

  7. Go to Setup  CSP Trusted Sites and Add https://www.google.com as shown below

Create custom Pre-Chat Form

Custom pre-chat form is required to add the Google reCAPTCHA. Detailed explanation for creating custom Pre-Chat Form can be referenced here.

  1. Create Static Resource botPreChatFormCSS: First step is to create a static resource. We will use this static resource to left align the error message on our pre-chat form.
    botPreChatFormCSS.css
    .text-align .slds-form-element__help {
        text-align: left;
    }
    botPreChatFormCSS.resource-meta.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <StaticResource xmlns="http://soap.sforce.com/2006/04/metadata">
        <cacheControl>Public</cacheControl>
        <contentType>text/css</contentType>
    </StaticResource>
  2. Create LWC botPreChatForm: Next is to create a lightning web component botPreChatForm which will import the lightningsnapin/basePrechat
    botPreChatForm.html
    <template>
        <div if:true={hasFields}>
            <div class="formContent embeddedServiceSidebarForm">
                <div class="slds-var-p-around_medium">
                    <template for:each={fields} for:item="field">
                        <div key={field.name} class="slds-var-p-bottom_small">
                            <span class="slds-form-element__label slds-no-flex slds-float_left">
                                <span if:true={field.required}
                                    class="slds-text-color_destructive slds-var-p-right_xxx-small">*</span>
                                {field.label}
                            </span>
                            <lightning-input name={field.name} type={field.type} label={field.label} variant="label-hidden"
                                class="text-align" value={field.value} required={field.required}>
                            </lightning-input>
                        </div>
                    </template>
                    <div class="recaptcha slds-var-p-top_medium"></div>
                    <div if:true={recaptchaAuthenticationError}
                        class="slds-form-element__label slds-no-flex slds-float_left slds-text-color_destructive">Please
                        Select Recaptcha</div>
                </div>
            </div>
            <div class="buttonWrapper embeddedServiceSidebarForm">
                <button type="button" onclick={handleStartChat}
                    class="slds-button slds-button--neutral startButton uiButton--default uiButton embeddedServiceSidebarButton">
                    <span class="label bBody">{startChatLabel}</span>
                </button>
            </div>
        </div>
    </template>
    botPreChatForm.js: Here we are using document.addEventListener to listen to CustomEvents fired from the script that we included in the Head Markup in our Experience Builder.
    import BasePrechat from 'lightningsnapin/basePrechat';
    import { api, track } from 'lwc';
    import startChatLabel from '@salesforce/label/c.StartChat';
    import botPreChatFormCSS from "@salesforce/resourceUrl/botPreChatFormCSS";
    import { loadStyle } from "lightning/platformResourceLoader";
    
    export default class BotPreChatForm extends BasePrechat {
        @api prechatFields;
        @track fields;
        @track namelist;
        startChatLabel;
        recaptchaAuthenticated;
        recaptchaAuthenticationError;
        
        constructor() {
            super();
            document.addEventListener("grecaptchaVerified", (evt) => {
                this.listenForGrecaptchaVerified(evt)
            });
            document.addEventListener("grecaptchaExpired", (evt) => {
                this.listenForGrecaptchaExpired(evt)
            });
            document.addEventListener("grecaptchaError", (evt) => {
                this.listenForGrecaptchaError(evt)
            });
        }
    
        //Set the button label and prepare the prechat fields to be shown in the form.
        connectedCallback() {
            this.startChatLabel = startChatLabel;
            this.recaptchaAuthenticated = false;
            this.recaptchaAuthenticationError = false;
            
            if (this.prechatFields) {
                this.fields = this.prechatFields.map(field => {
                    field = JSON.parse(JSON.stringify(field));
                    field.type = field.type.replace('input', '');
                    const { label, name, value, required, maxLength, type } = field;
                    return { label, name, value, required, maxLength, type };
                });
                this.namelist = this.fields.map(field => field.name);
            }
        }
    
        disconnectedCallback() {
            this.removeEventListeners();
        }
    
        //Focus on the first input after this component renders.
        renderedCallback() {
            Promise.all([
                loadStyle(this, botPreChatFormCSS),
            ]).then(() => { });
            if(!this.inputFocused) {
                let lightningInputElement = this.template.querySelector("lightning-input");
                if (lightningInputElement) {
                    lightningInputElement.focus();
                    this.inputFocused = true;
                };
            }
            if (!this.recaptchaRendered) {
                let divElement = this.template.querySelector('.recaptcha');
                let payload = { element: divElement, prefix: 'bot' };
                document.dispatchEvent(new CustomEvent("grecaptchaRender", { "detail": payload }));
                this.recaptchaRendered = true;
            }
        }
    
        removeEventListeners() {
            document.removeEventListener("grecaptchaVerified", () => { });
            document.removeEventListener("grecaptchaExpired", () => { });
            document.removeEventListener("grecaptchaError", () => { });
        }
    
        listenForGrecaptchaVerified(event) {
            this.setRecaptchaAuthentication(event.detail.message);
            this.recaptchaAuthenticated = true;
        }
    
        listenForGrecaptchaExpired(event) {
            this.setRecaptchaAuthentication(event.detail.message);
        }
    
        listenForGrecaptchaError(event) {
            this.setRecaptchaAuthentication(event.detail.message);
        }
    
        setRecaptchaAuthentication(message) {
            this.recaptchaAuthenticated = false;
            this.recaptchaAuthenticationError = true;
            if (message && message.toUpperCase() === 'BOTSUCCESS') {
                this.recaptchaAuthenticated = true;
                this.recaptchaAuthenticationError = false;
            }
        }
        
        // getter property to render the prechat fields
        get hasFields() {
            return this.fields && this.fields.length > 0;
        }
    
        //On clicking the 'Start Chatting' button, send a chat request.
        handleStartChat(event) {
            event.preventDefault();
            let inputElements = this.template.querySelectorAll("lightning-input");
            this.recaptchaAuthenticationError = false;
            if (!this.recaptchaAuthenticated) {
                this.recaptchaAuthenticationError = true;
            }
            if (inputElements && this.checkInputValidity(inputElements) && this.recaptchaAuthenticated) {
                inputElements.forEach(input => {
                    this.fields[this.namelist.indexOf(input.name)].value = input.value;
                });
                if (this.validateFields(this.fields).valid) {
                    this.removeEventListeners();
                    this.startChat(this.fields);
                } else {
                    // Error handling if fields do not pass validation.
                }
            }
        }
    
        checkInputValidity(inputElements) {
            const allValid = [...inputElements].reduce(
                (validSoFar, inputCmp) => {
                    inputCmp.reportValidity();
                    return validSoFar && inputCmp.checkValidity();
                }, true);
            return allValid;
        }
    }
    botPreChatForm.css
    :host {
        display: flex;
        flex-direction: column;
    }
    
    lightning-button {
        padding-top: 1em;
    }
    
    .embeddedServiceSidebarForm.formContent {
        width: 100%;
        height: 350px;
        overflow-y: auto;
        position: relative;
        background-color: var(--lwc-esColorBackgroundInverse,#fff);
        border-radius: 0 0 8px 8px;
    }
    
    .embeddedServiceSidebarForm.buttonWrapper {
        text-align: center;
        padding: 30px 24px 24px;
        margin: 0;
        width: 100%;
        position: absolute;
        box-sizing: border-box;
        bottom: 0;
        background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) calc(100% - 77px), rgba(255, 255, 255, 1) 100%);
    }
    
    .embeddedServiceSidebarButton {
        position: relative;
        border: none;
        background: var(--lwc-colorBrand,#2574A9);
        text-shadow: none;
        box-shadow: none;
        border-radius: 4px;
        transition: background .4s ease;
        color: var(--lwc-colorTextInverse,#FFFFFF);
        font-size: var(--lwc-esFontSizeMedium,1em);
        font-weight: var(--lwc-fontWeightRegular,400);
        font-family: var(--lwc-fontFamily,Lato),sans-serif;
        width: 100%;
        margin: 0;
        height: 44px;
    }
    botPreChatForm.js-meta.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
        <apiVersion>52.0</apiVersion>
        <isExposed>true</isExposed>
        <targets>
            <target>lightningSnapin__PreChat</target>
        </targets>
    </LightningComponentBundle>
  3. Override Standard Pre-Chat Form: Now as we have created our custom pre-chat form, Click here to know how to override the standard pre-chat form from my previous blog.

Add the Embedded Service Chat in our Experience Builder

Lastly we need to add the Einstein Bot to our public facing site. 

  1. Login to Sandbox/Production org.
  2. Go to Digital Experiences → All Sites and open the Builder for next to Site URL.
  3. Go to Components Embedded Service Chat and drag-drop to builder canvas.
  4. Finally Publish your community.

DEMO:

As we are all done with all the required set up and customization around custom pre-chat needed for Einstein Bot and updated our Experience Builder with the Embedded Service Chat and published our site, finally it's time to have a demo of our implementation. 

This Einstein Bot demo
  1. Covers the validation for the pre-chat fields.
  2. Doesn't allow to start the chat until we verify the Google reCAPTCHA. While verifying the Google reCAPTCHA, we may sometime be forced to solve the challenge like the image selection or audio CAPTCHA. 
  3. Once all verified successfully then only we will be allowed to start the chat.
Demo 1 : Without additional challenge, simple I am not a Robot Challenge

Demo 2
 : With additional challenge

If you like this blog content and find inciteful, please comment and let me know. 

Comments

  1. Validation for email is missing

    ReplyDelete
  2. Thanks for taking the time to discuss this, I feel strongly about it and love learning more on this topic.
    anti captcha key

    ReplyDelete
  3. Hi Thanks for great post!
    I have implemented same thing working fine now the issue is recaptcha image challenge comes inside chatbox outside like you shown above. could you help?

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Hi Thanks for great post!
    I have implemented same thing working fine now the issue is recaptcha image challenge comes inside chatbox not outside like you shown above. could you help? what I am missing.

    ReplyDelete

Post a Comment