Home  /  Documentation  /  Tutorials  /  Putting your web application online – Part 6 – Advanced anti-bot protection

Putting your web application online – Part 6 – Advanced anti-bot protection

Previous part: Putting your web application online – Part 5 – Database and USERS


Starting point:You have a Node++ application on the server with USERS module.
This part result:Your application is protected against fake accounts and spamming bots.
Skill level:Middle
Time:About 15 minutes

As long as we didn't establish means to write to us, protecting our application has mostly been confined to the operating system level. The minute we published our first submit button, we are on a very different territory. We already have some protection – mainly against brute-force and injections. But that's still not enough.

When Budgeter was getting somewhat popular, suddenly I could see dozens of new accounts daily. It turned out, they had random strings as their user names, just as if they were generated. They didn't show any further activity. When investigating logs, it was clear that they were just bots – for example there were no resources' reading besides create_acc page. The nasty part of it was that fake registrations seemed to use real email addresses. And Budgeter welcomes every new member with an email – it could soon face a significant negative flagging!

Apart from fake accounts, there was also increasing volume of spam, coming through contact form.

As there was already a CSRF token, I had to come up with something more sophisticated to tell the difference between human and bot. I don't like puzzles and captchas just drive me crazy! There had to be some other way.

The solution described here has been working for several years now. Breaking this protection would require custom, non-trivial programming work on those bots. I expect it may happen sooner or later, for now though, it does its job in 100% of cases.


Steps to take:

  1. JavaScript
  2. Add secret input to the session data
  3. Generate secret input on session start
  4. Call ahi after form is displayed
  5. Verify hidden input's name on form submission

Step 1 – JavaScript

We will need a simple JS function that adds new hidden input to the form. Let's call it ahi like add hidden input:

// -------------------------------------------------------------------------- // add hidden input // anti-bot facility // -------------------------------------------------------------------------- function ahi(n) { let f = document.getElementsByTagName("form")[0]; // find a form if ( f ) // if found { let i = document.createElement("input"); i.name = n; i.type = "hidden"; f.appendChild(i); } }

Note that it's actually the input name that matters here, not the content.


Step 2 – Add secret input to the session data

In npp_app.h there is a custom structure for application session data, that we can extend. Replace dummy or add new member:

#define SECRET_INPUT_LEN 7 /* app session data */ /* accessible via SESSION_DATA macro */ typedef struct { char secret_input[SECRET_INPUT_LEN+1]; } app_session_data_t;

Step 3 – Generate secret input on session start

Set secret_input value to random string:

bool npp_app_session_init() { strcpy(SESSION_DATA.secret_input, npp_random(SECRET_INPUT_LEN)); return true; }

Step 4 – Call ahi after form is displayed

We can add this to the footer function (if we have one). It actually adds our hidden input to the form:

/* -------------------------------------------------------------------------- Render footer -------------------------------------------------------------------------- */ void footer() { // ... if ( REQ("register") || REQ("contact") ) { DBG("Adding secret input [%s]", SESSION_DATA.secret_input); OUT("<script>"); OUT("ahi('%s');", SESSION_DATA.secret_input); /* add hidden input */ OUT("</script>"); } OUT("</body>"); OUT("</html>"); }

Step 5 – Verify hidden input's name on form submission

After we have verified that both valid CSRFT and secret input were present in the form, we can save the message in the database:

static void do_contact() { int ret = OK; if ( !CSRFT_OK ) { WAR("CSRFT validation failed. Bot from %s has been denied sending message.", SESSION.ip); ret = ERR_CSRFT; } if ( !QS(SESSION_DATA.secret_input, NULL) ) { WAR("No secret input. Bot from %s has been denied sending message.", SESSION.ip); ret = ERR_CSRFT; } if ( ret == ERR_CSRFT ) { RES_LOCATION("/contact?msg=%d", MSG_MESSAGE_SENT); /* pretend everything's fine */ QSVAL m; /* out of curiosity -- log sanitized message */ if ( QS("msg_box", m) ) INF("msg_box [%s]", npp_filter_strict(m)); return; } ret = npp_usr_send_message(); if ( ret == OK ) RES_LOCATION("/contact?msg=%d", MSG_MESSAGE_SENT); else RES_LOCATION("/contact?msg=%d", ret); }

We also need to add this verification to do_register.


That's it for now! We are protected against fake accounts and spamming bots.


Next part: Putting your web application online – Part 7 – Sending email


Is something wrong here? Please, let us know! Envelope