Thursday, April 12, 2018

How to create custom captcha in nodejs

The captcha is most useful to identify human from machine input to prevent thwarting spam and automated extraction of data from websites.

To prevent automated code/script for submitting and information/form, captcha is used.

We can use any pre-defined module or code to prevent spam data but here, we will discuss how to create our own custom captcha in nodeJs and used it in HTML using javascript.

Here, I have used angularjs 1 with nodejs in MEAN  STACK single page application, but you can use any front-end framework or template engine like as angularjs 2, angularjs 3, angularjs 4, angularjs 5, ejs, etc.

EX: Demo

register.html
<div ng-controller="UserRegisterController">
    <h2>Register</h2>
    <form ng-submit="userRegister()" name="myForm">
        <div>
            <input type="text" ng-model="user.user_name" name="user_name" placeholder="Enter your name" />
            <span ng-show="myForm.$dirty && myForm.user_name.$error.required">Please enter user name</span>
        </div>
        <div>
            <input type="email" ng-model="user.email" name="email" placeholder="Enter your Email" required/>
            <span ng-show="myForm.$dirty && myForm.email.$error.required">Please enter email id</span>
            <span ng-show="myForm.$dirty && myForm.email.$invalid">Please enter valid email id</span>
        </div>
        <div>
            <input type="password" ng-model="user.password" name="password" placeholder="Enter password" required />
            <span ng-show="myForm.$dirty && myForm.password.$error.required">Please enter password</span>
        </div>
        <div>
            <div class="captcha-box">
                <img ng-src="{{captchaImgSrc}}" class="margin_top margin_bottom">
                <a href="javascript:void(0);" ng-click="getRecaptcha();">new captcha</a>
            </div>
            <div>
                <input type="text" name="captcha" ng-model="user.captcha" placeholder="Enter captcha code" required />
                <span ng-show="myForm.$dirty && myForm.captcha.$error.required">Please enter captcha</span>
                <div ng-show="wrongCaptchError">Please enter correct captcha</div>
            </div>
        </div>
        <input type="submit" value="Submit" ng-disabled="myForm.$invalid">
    </form>
</div>

UserRegisterController.js
myapp.controller('UserRegisterController', function($scope, $http, $location) {
    /*
     *Get captcha
     * */
    $scope.getRecaptcha = function() {
        $http({
                url: "/api/getRecaptcha",
                method: "GET",
                cache: false,
            })
            .success(function(res) {
                    $scope.captchaImgSrc = res.captchaImgSrc;
            })
            .error(function() {
                console.log("error occurs");
            });
    };

    /*
     *Call this function on load
     */
    $scope.getRecaptcha();

    /*
     *Submit user registration data
     */
    $scope.userRegister = function() {
        $scope.wrongCaptchError = false;
        $http.post('/api/userRegister', $scope.user)
            .success(function(res) {
                   if(res.message === undefined){
                       if(res.message === "wrongCaptchError"){
                           $scope.wrongCaptchError = true;
                       }else{
                           console.log("err.......");
                           console.log(res.message);
                       }
                   }else{
                       console.log("register successful");
                      $location.path('#index'); // redirect to home page or login page
                   }
             
            })
            .error(function(res) {
                console.log('Error: ' + res);
            });
    };
});

here, if you are using any other template engine or framework you can make and call API as per your web application structure.

before start, this demo install captchapng module

api.js (back-end side in nodejs using expressjs)
/**
 * Requires node libraries
 */
var express = require('express');
var router = express.Router();
var captchapng = require('captchapng');

/*
 * Create custom captcha
 */
router
    .route("/api/getRecaptcha")
    .get(
            function (req, res) {
                var captchaValue = parseInt(Math.random()*9000+1000);
                var p = new captchapng(80,40, captchaValue); // width,height,numeric captcha
                p.color(50, 0, 40, 25);  // First color: background (red, green, blue, alpha)
                p.color(20, 40, 100, 255); // Second color: paint (red, green, blue, alpha)
                var img = p.getBase64();
                captchaImgSrc = "data:image/jpeg;base64,"+img;
                res.cookie('captcha', captchaValue, {maxAge: 1 * 60 * 60 * 1000, httpOnly: true}); //24 * 60 * 60 * 1000 // 24 hours
                res.json({status: 1, captchaImgSrc : captchaImgSrc});
            });

/*
 * User register
 */
router
    .route("/api/userRegister")
    .post(
        function(req, res) {
            //check recaptch is validate or not
            if (req.cookies["captcha"] !== undefined) {
                if (req.body.captcha !== undefined) {
                    if (req.cookies["captcha"] === req.body.captcha) {
                        //Recaptcha validated
                        // your registration code here
                        ...
                        ...
                        ...
                        ...
                    } else {
                        res.json({ message: "wrongCaptchError" });
                    }
                } else {
                    res.json({ message: "Please enter captcha" });
                }
            } else {
                res.json({ message: "something wrong" });
            }
        });

module.exports = router;

here,
->we have use captchapng nodejs module for generating numeric captcha
->Store captcha value in "captcha" cookie. here you can give any name of the cookie as you want.
->Use httpOnly: true option for make cookie secure, if you will make cookie insecure then it can read and modify from front-end javascript which may be a hack.
->if you do not want use cookie then you can use/store in session for verifying after form submits in user registration API.

By using captchapng module you can make only numeric captcha. but not used alphabet characters.

if you want to make numeric and alphabet characters in captcha then modify bellow API "/api/getRecaptcha".

In this, we will use gm module instead of captchapng. before start, this demo install gm(GraphicsMagick) module

Ex: Modified above api.js file
/**
 * Requires node libraries
 */
var express = require('express');
var router = express.Router();
var gm = require("gm");

/*
 * Create custom captcha
 */
router
    .route("/api/getRecaptcha")
    .get(
        function(req, res) {
            //Captcha using GM module
            var text = [];
            var captchaValue = "";
            var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
            for (var i = 0; i < 5; i++) {
                text.push(possible.charAt(Math.floor(Math.random() * possible.length)));
                captchaValue = captchaValue + "" + captchaValue;
            }
            gm(80, 40, "#808080")
                .drawText(15, 20, text[0])
                .drawText(30, 20, text[1])
                .drawText(45, 20, text[2])
                .drawText(60, 20, text[3])
                .font("Helvetica.ttf", 20)
                //.fontSize(20)
                .toBuffer('PNG', function(err, buffer) {
                    if (err) {
                        console.log("Something wrong");
                    } else {
                        captchaImgSrc = "data:image/jpeg;base64," + buffer.toString('base64');
                        res.cookie('captcha', captchaValue, { maxAge: 1 * 60 * 60 * 1000, httpOnly: true }); //24 * 60 * 60 * 1000 // 24 hours
                        res.json({ status: 1, captchaImgSrc: captchaImgSrc });
                    }
                });
        });

/*
 * User register
 */
router
    .route("/api/userRegister")
    .post(
        function(req, res) {
            //check recaptch is validate or not
            if (req.cookies["captcha"] !== undefined) {
                if (req.body.captcha !== undefined) {
                    if (req.cookies["captcha"] === req.body.captcha) {
                        //Recaptcha validated
                        // your registration code here
                        ...
                        ...
                        ...
                        ...
                    } else {
                        res.json({ message: "wrongCaptchError" });
                    }
                } else {
                    res.json({ message: "Please enter captcha" });
                }
            } else {
                res.json({ message: "something wrong" });
            }
        });

module.exports = router;

here, gm nodejs module is used for generating alphanumeric captcha.
gm module has many reach methods/functions to generate a new image.

for more information about gm review https://cnpmjs.org/package/gm link.

By using gm you can also use any font color and put text in any where to image and put it in any angle or degree.

1 comment: