Monday, November 27, 2023

How to use Google reCAPTCHA v3 in Lightning Web Component

We have many applications that do require some authentication to prevent the attack vectors. To stop flooding the application with unnecessary attacks(eg. Robots running the scripts), 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


Google Configuration

  1. Go to https://www.google.com/recaptcha
  2. Click on the ‘Admin console’ button
  3. Click the ‘+’ create icon if you already have sites configured
  4. Enter a Label and select reCAPTCHA v3, v2 Checkbox or v2 Invisible.
  5. Add your custom or force.com community domain. (You can also add additional domains so that it will function in Experience Builder)
  6. Accept the terms of service and click the Submit button

Screen Shot 2020-04-19 at 3.24.06 PM.png
You will need to use your resulting keys in the code examples below. The keys are only valid for the type of reCAPTCHA selected during creation. If you want to test all 3 examples below, you must create each type in the admin console.
Screen Shot 2020-04-18 at 12.47.50 PM.png Site Key - This public key is intended to be exposed on your site and should be pasted directly in the JavaScript Head Markup examples (replace text ‘reCAPTCHA_site_key’).

Secret Key - This private key should live on the server-side only and be stored in a Custom Setting, Custom Metadata Type or Apex class (replace text ‘reCAPTCHA_secret_key’).

 

Experience Builder Settings

You will see Content Security Policy (CSP) errors as you implement the reCAPTCHA code examples. In Experience Builder → Settings → Security, add the trusted sites shown below and click the ‘Whitelist’ button as items show in the ‘CSP Errors’ list.
Screen Shot 2020-04-18 at 2.16.33 PM.png
 

There are 4 steps to integrate reCAPTCHA v3 to a Lightning Web Component

1. Create html static resource with reCAPTCHA

reCAPTCHAv3.html

<html>
    <head>
        <title></title>reCAPTCHA html resource</title>
        <script src="https://www.google.com/recaptcha/api.js?render=reCAPTCHA_site_key"></script>
    </head>
    <body>
        <input type="hidden" name="recaptcha_response" id="recaptchaResponse"/>
        <script type="text/javascript">
            grecaptcha.ready(function() {
                var reCAPTCHA_site_key = "reCAPTCHA_site_key";
                grecaptcha.execute('reCAPTCHA_site_key', {action: 'submit'}).then(function(token) {
                    recaptchaResponse.value = token;
                    if (token == "") {
                        parent.postMessage({ action: "getCAPCAH", callCAPTCHAResponse : "NOK"}, "*");
                    } else {
                        parent.postMessage({ action: "getCAPCAH", callCAPTCHAResponse : token}, "*");
                    }
                });
            }
        </script>
    </body>
</html>

reCAPTCHAv3.resource-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<StaticResource xmlns="http://soap.sforce.com/2006/04/metadata">
    <cacheControl>Public</cacheControl>
    <contentType>text/html</contentType>
</StaticResource>

2. Create Lightning Web Component with an iframe to load the static resource

myLWC.html

<template>
    <iframe src={navigateTo} name="captchaFrame" onload={captchaLoaded}></iframe>
</template>

3. Create the Javascript controller for the Lightning Web Component

myLWC.js

import { LightningElement, track, api } from 'lwc';
import pageUrl from '@salesforce/resourceUrl/reCAPTCHAv3';
import isReCAPTCHAValid from '@salesforce/apex/reCAPTCHAv3ServerController.isReCAPTCHAValid';

export default class GoogleCapatcha extends LightningElement {
    @api formToken;
    @api validReCAPTCHA = false;

    @track navigateTo;
    captchaWindow = null;

    constructor(){
        super();
        this.navigateTo = pageUrl;
    }

    captchaLoaded(evt){
        var e = evt;
        console.log(e.target.getAttribute('src') + ' loaded');
        if(e.target.getAttribute('src') == pageUrl){

            window.addEventListener("message", function(e) {
                if (e.data.action == "getCAPCAH" && e.data.callCAPTCHAResponse == "NOK"){
                    console.log("Token not obtained!")
                } else if (e.data.action == "getCAPCAH" ) {
                    this.formToken = e.data.callCAPTCHAResponse;
                    isReCAPTCHAValid({tokenFromClient: formToken}).then(data => {
                        this.validReCAPTCHA = data;
                    });
                }
            }, false);
        } 
    }

}

4. Create Apex class to handle the server-side verification

reCAPTCHAv3ServerController.cls

public with sharing class reCAPTCHAv3ServerController {
    public reCAPTCHAv3ServerController(){

    }


    @AuraEnabled
    public static Boolean isReCAPTCHAValid(String tokenFromClient) {
        String SECRET_KEY = 'reCAPTCHA_secret_key';
        String RECAPTCHA_SERVICE_URL = 'https://www.google.com/recaptcha/api/siteverify';
        Http http = new Http();

        HttpRequest request = new HttpRequest();

        request.setEndpoint(RECAPTCHA_SERVICE_URL + '?secret=' + SECRET_KEY + '&response' + tokenFromClient);
        request.setMethod('POST');
        request.setHeader('Content-Length', '0');
        HttpResponse response = http.send(request);

        Map<String, Object> mapOfBody = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());

        Boolean success = (Boolean) mapOfBody.get('success');

        return success;
    }
}

No comments: