Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
WouterLemaire
Active Contributor

Introduction

When deploying a CAP application on BTP that is publicly accessible and involves user input, safeguarding it from automated bots is crucial. This can be effectively achieved through the implementation of reCAPTCHA, a Google service designed to distinguish between human users and bots. As illustrated by my own public website, https://wouter.lemaire.tech , developed using CAP and hosted on BTP, the integration of reCAPTCHA adds a vital layer of security to ensure a seamless user experience.

In this blog post, I’ll demonstrate how I implemented this for my own website.

 

Prerequisite

Before we start implementing reCAPTCHA, we first have to create a reCAPTCHA site. This can be done on the reCAPTCHA website: https://www.google.com/recaptcha/about/

(You’ll need to login with a google account first)

Go to “v3 Admin Console”:

WouterLemaire_38-1709335190637.png

Click on the “+” button to create a new site:

WouterLemaire_39-1709335202401.png

Here you can select a reCAPTCHA type, this will have impact on how it will be shown on your website. Do you want the end-user to click on a tickbox first? Do you prefer to have a smarter version based on score? Depending on your needs, you might want to take a different type:

WouterLemaire_40-1709335227978.png

Once created, it will provide you a site key and secret key. Store them somewhere, you’ll need it during the implementation!

WouterLemaire_41-1709335243698.png

Provide the domains for the website that can trigger the reCAPTCHA. I’ve put it also the BTP domains to be able to test from in VSCode and BAS but also to test if it works before I had a custom domain.

WouterLemaire_42-1709335262987.png

You can also configure the security settings depending on your needs:

WouterLemaire_43-1709335276051.png

Once you have done this, we can start using reCAPTCHA in our CAP application.

 

How it works

Before starting on the implementation, an overview of what we will be implementing. reCAPTCHA requires implementation on the frontend (UI5 in our case) and on the backend (service layer in CAP). It is up to us to implement it correct to secure the application with reCAPTCA.

WouterLemaire_44-1709335296938.png

Steps to be implemented to make sure that it follows the correct flow:

  1. Show the reCAPTCHA tickbox (in case of v2 tickbox type) on the UI
  2. Use the google reCAPTCHA api to send a request “userverify” to the google reCAPTCHA service to validate that it is user by using the sitekey (the first key from in the prerequisites section)
  3. The UI will receive back a captcha token from the google reCAPTCHA api
  4. The UI needs to use this captcha token for every request to the CAP backend that needs to be secured
  5. The CAP backend needs to verify this captcha token by sending it together with the secret key (the second key from the prerequisites) to the google reCAPTCHA api.
  6. The google reCAPTCHA api will send back if the captcha token is valid or not.

When the result of step 6 is successful you can continue with your own implementation assuming that the input is coming from a real user and not a bot.

 

How to implement

Let’s have some fun! We’re going to start from top to bottom which brings us first to the UI. In my example, I use the reCAPTCHA tickbox in a dialog that has his own controller. For this I added a FlexBox in the dialog as placeholder for the reCAPTCHA tickbox.

WouterLemaire_45-1709335319234.png

Here I added one line with the id “captcha” to use it later on to connect it with reCAPTCHA:

WouterLemaire_46-1709335328802.png

Next, we need to implement the rendering of the reCAPTCHA tickbox from in the controller:

WouterLemaire_47-1709335337710.png

My dialog controller has a custom function “onBeforeShow” that will be called every time it is being opened. In this function I added the following code to render the reCAPTCHA tickbox. The tickbox requires the dialog to be rendered before it can be shown. Therefore, I use the event “afterOpen” to render the tickbox. The reCAPTCHA tickbox requires the dom id of the flexbox (not just the UI5 id), sitekey and a callback. The callback is used when a user selects the tickbox. When a user clicks on the tickbox, it will send a request automatically to the google reCAPTCHA api to validate if it is a real user and response with a captcha token which will be received in the callback.

WouterLemaire_48-1709335345243.png

 

 

(this.dialog.fragment as Dialog).attachAfterOpen((event: UI5Event) => {
  const id = (this.fragmentById(this.viewController, "Book", "captcha") as FlexBox)?.getDomRef()?.id;
  grecaptcha.ready(() => {
    grecaptcha.render(id, {
      'sitekey': '<sitekey>',
      'callback': (response: string) => this.verifyCallback(response)
    });
  });
});

 

 

In the callback, the response contains the captcha token and I store it in a JSON Model.

WouterLemaire_49-1709335501099.png

 

 

public verifyCallback(response: string) {
    const bookModel = this.dialog.fragment.getModel("book")! as JSONModel;
    bookModel.setProperty("/captcha", response);
}

 

 

Once the user wants to save or submit his input, the token needs to be used in the request to the CAP backend. In my example, I’m using an action with a property for captcha but this could also be an entity or it could also be passed as a header parameter:

WouterLemaire_50-1709335514691.png

 

 

const bookModel = this.dialog.fragment.getModel("book")! as JSONModel;
const mailCtx = (this.viewController.getView()?.getModel() as ODataModel)?.bindContext("/sendMail(...)");
mailCtx.setParameter("mailFrom", bookModel.getProperty("/mail"));
mailCtx.setParameter("topic", bookModel.getProperty("/topic"));
mailCtx.setParameter("description", bookModel.getProperty("/description") || "");
mailCtx.setParameter("captcha", bookModel.getProperty("/captcha") || "");
await mailCtx.execute();

 

 

That’s it for the UI! But before starting on the backend implementation, we need to add some config in the package.json:

WouterLemaire_51-1709335528400.png

We’ll have to send a request from the backend to the google reCAPTCHA api. It is recommended to declare external services/api’s in the package.json. A destination could also be used but in this case doesn’t has much added value:

WouterLemaire_52-1709335538772.png

In the srv layer of our CAP backend we’ll need to create the action:

WouterLemaire_53-1709335547089.png

In the cat-service.cds, I only declared an action:

WouterLemaire_54-1709335553086.png

In the event handler of this action, I use the external service configuration from the package.json. this connection can be used to send a request that verifies the captcha token together with the secret key. (I’m sending it as part of the body and url parameters, normally url parameters is enough)

The result of this request will verify if it was successful and the end-user is validated as a real user:

WouterLemaire_55-1709335559263.png

 

 

const { data: { mailFrom, topic, description, captcha }}=req;
const captchaAPI = await cds.connect.to("RECAPTCHA_API");
const data = {
    secret: "<captcha secret key>",
    response: captcha
};
const validateCaptcha = await captchaAPI.send({ method: 'POST', path: `/siteverify?secret=${data.secret}&response=${data.response}`, data });
console.log(JSON.stringify(validateCaptcha))
if (!validateCaptcha.success) {
    console.error("404 Captcha not correct!");
    req.error(404, `Your session is not valid anymore, try to refresh your browser.`);
}

 

 

That's it! 🙂 

 

Result

Feel free to try it on my website and put in the description that it is just for testing purpose 😉

Go to https://wouter.lemaire.tech/ and click on “Book me”:

WouterLemaire_56-1709335581022.png

 

 

 

2 Comments
Labels in this area