The fourth version of visualCaptcha was really good: great security and innovative accessibility solution, mobile-friendly and retina-ready design, self-hosted and open-source!
But it supported only PHP and had some unwanted dependencies. So the goal for a new version was:
to get it super easy to implement, require no dependencies, and backend agnostic (meaning you could easily plug it in to any backend code), while providing a few backend samples.
visualCaptcha 5.0 is free from jQuery or jQuery UI dependencies now.
Library is available across multiple backend languages and technologies. It's supporting:
- PHP with Composer/Packagist package;
- Node JS with NPM package;
- Ruby with RubyGem;
- Meteor JS with Meteorite package;
- WordPress with WordPress plugin.
visualCaptcha use Bower to provide a few frontend integration solutions as well as library core which everyone can use to build integration with other frameworks:
The UX and UI were improved for the new version of visualCaptcha. Also we have prepared several demo pages using different backend and frontend technologies:
- PHP and jQuery Demo
- Node.js and Vanilla JS Demo
- Ruby and Angular JS Demo
- Meteor JS and Vanilla JS Demo
- Multiple captchas PHP and jQuery Demo.
We've created a new web site with amazing fresh responsive design and retina-ready images.
CleverStack uses full-stack JavaScript technologies with Node JS and Angular JS. Thus the appropriate libraries are Angular JS version of visualCaptcha and NPM package for Node JS.
And, of course, we need installed CleverStack CLI and created application.
In terminal open backend folder of application and run next installation command for visualCaptcha npm module:
npm install --save visualcaptchaAs well we need a client-sessions module for visualCaptcha initialising, run the next command to install it:
npm install --save client-sessionsIn previous step we have installed client-sessions npm module. And now we need to initialise it correct into backend/index.js file. First of all add next line on top of the file to require this module:
var sessions = require( 'client-sessions' );
Then we need to add next code into env.app.configure(function() { /* ... */ }); callback function to initialise the sessions:
// Configure the app before routes
env.app.configure(function() {
/* ... here is other existed app configuration ... */
// visualCaptcha sessions
env.app.use( env.express.cookieParser() );
env.app.use( sessions({
cookieName: 'session',
secret: 'someRandomSecret',
duration: 86400000,// 24h in milliseconds
cookie: {
path: '/',
httpOnly: true,
secure: false,
ephemeral: false
}
}) );
});
We need to create a simple folder structure for visualCaptcha backend module inside the backend/modules/ folder of the application:
.
|____visualCaptcha-module
|____controllers
| |____CaptchaController.js
|____module.js
|____package.json
|____routes.js
Where:
- package.json — is the main file for CleverStack initialisation and should look like this one. Notice that name need to be equal to the visualCaptcha module folder name:
{
"name": "visualCaptcha-module",
"version": "0.0.1",
"private": true,
"dependencies": {},
"author": {
"name": "Clevertech",
"email": "info@clevertech.biz",
"web": "http://www.clevertech.biz"
},
"collaborators": [
"Alexander Bykhov <alex.bykhov@clevertech.biz>"
],
"description": "visualCaptcha module for the Cleverstack backend.",
"keywords": [
"cleverstack",
"cleverstack-module",
"cleverstack-backend",
"clevertech",
"backend",
"visualCaptcha",
"captcha"
],
"main": "module.js",
"devDependencies": {}
}- module.js — is for visualCaptcha module definition:
var ModuleClass = require( 'classes' ).ModuleClass
, Module;
Module = ModuleClass.extend({});
// name of module must be *equal* to the module folder name: 'visualCaptcha-module'
module.exports = new Module( 'visualCaptcha-module', injector );Also we need to add name of our module into the backend/package.json file in bundledDependencies section for correct cleverStack module initialisation:
"bundledDependencies": [
"visualCaptcha-module"
]- routes.js — module of routes for visualCaptcha initialisation and validation. It uses CaptchaController that described below.
module.exports = function( app, CaptchaController ) {
// visualCaptcha initialisation routes
app.get('/captcha/start/:howmany', CaptchaController._start );
app.get('/captcha/audio/:type?', CaptchaController._getAudio );
app.get('/captcha/image/:index', CaptchaController._getImage );
// CORS fix for submission
app.options('/user/login', CaptchaController._options );
// Submission demo route
app.post('/user/login', CaptchaController._trySubmission );
};- controllers/CaptchaController.js — main controller for visualCaptcha module. It has the following structure:
module.exports = function() {
var visualCaptcha,
// allowOrigin — front-end origin url, it's used to fix the CORS.
// It can take the value of '*' as well.
allowOrigin = 'http://localhost:9000';
return {
// CORS fix
_options: function( req, res, next ) {
/* ... */
},
_start: function( req, res, next ) {
/* ... */
},
_getImage: function( req, res, next ) {
/* ... */
},
_getAudio: function( req, res, next ) {
/* ... */
},
_trySubmission: function( req, res, next ) {
/* ... */
}
};
};_options — is a function for fixing the CORS. It sets up correct headers for CORS and sends response with status 200:
// CORS fix
_options: function( req, res, next ) {
res.header( 'Access-Control-Allow-Origin', allowOrigin );
res.header( 'Access-Control-Allow-Methods', 'POST' );
res.header( 'Access-Control-Allow-Headers', 'accept, content-type' );
res.header( 'Access-Control-Allow-Credentials', true );
res.send( 200 );
},_start — is a function for visualCaptcha initialising and refreshing:
_start: function( req, res, next ) {
// After initializing visualCaptcha, we only need to generate new options
if ( ! visualCaptcha ) {
visualCaptcha = require( 'visualcaptcha' )( req.session, req.query.namespace );
}
// Generation visualCaptcha data
visualCaptcha.generate( req.params.howmany );
// CORS fix
res.header( 'Access-Control-Allow-Origin', allowOrigin );
res.header( 'Access-Control-Allow-Credentials', true );
// We have to send the frontend data to use on POST.
res.send( 200, visualCaptcha.getFrontendData() );
},_getImage — fetches and streams generated visualCaptcha image by index:
_getImage: function( req, res, next ) {
var isRetina = false;
// Default is non-retina
if ( req.query.retina ) {
isRetina = true;
}
visualCaptcha.streamImage( req.params.index, res, isRetina );
},_getAudio — fetches and streams generated visualCaptcha audio file:
_getAudio: function( req, res, next ) {
// Default file type is mp3, but we need to support ogg as well
if ( req.params.type !== 'ogg' ) {
req.params.type = 'mp3';
}
visualCaptcha.streamAudio( res, req.params.type );
},_trySubmission — is an example function for visualCaptcha validation:
// Try to validate the captcha
_trySubmission: function( req, res, next ) {
var namespace = req.query.namespace,
frontendData,
queryParams = [],
imageAnswer,
audioAnswer,
responseStatus,
status,
result = {};
frontendData = visualCaptcha.getFrontendData();
// Add namespace to result, if present
if ( namespace && namespace.length !== 0 ) {
result.namespace = namespace;
}
// It's not impossible this method is called before visualCaptcha is initialized,
// so we have to send a 404
if ( typeof frontendData === 'undefined' ) {
result.status = 'noCaptcha';
responseStatus = 404;
} else {
// If an image field name was submitted, try to validate it
if ( ( imageAnswer = req.body[ frontendData.imageFieldName ] ) ) {
result.type = 'image';
if ( visualCaptcha.validateImage( imageAnswer ) ) {
result.status = 'valid';
responseStatus = 200;
} else {
result.status = 'invalid';
responseStatus = 403;
}
} else if ( ( audioAnswer = req.body[ frontendData.audioFieldName ] ) ) {
// If an audio field name was submitted, try to validate it
result.type = 'audio';
// We set lowercase to allow case-insensitivity, but it's actually optional
if ( visualCaptcha.validateAudio( audioAnswer.toLowerCase() ) ) {
result.status = 'valid';
responseStatus = 200;
} else {
result.status = 'invalid';
responseStatus = 403;
}
} else {
result.status = 'failedPost';
responseStatus = 500;
}
}
// CORS fix
res.header( 'Access-Control-Allow-Origin', allowOrigin );
res.header( 'Access-Control-Allow-Credentials', true );
res.send( responseStatus, {
captcha: result
});
}Backend CleverStack module for visualCaptcha is done and implemented.
In terminal open frontend folder of application and run next installation command for Angular JS version of visualCaptcha frontend library:
bower install --save visualcaptcha.angularTo include visualcaptcha.angular library we need to add alias for the visualcaptcha.angular library path into the modules/main.js file:
paths: {
/* ... */
visualCaptcha: '../components/visualcaptcha.angular/visualcaptcha.angular'
},And then add 'visualCaptcha' into Require JS dependensies array:
require([
'angular',
'ngRoute',
'ngResource',
'ngSanitize',
'http-auth-interceptor',
'bootstrap',
'visualCaptcha', // <-- add this after 'angular'
// Init
'application',
], function (angular) {
// ...
});We don't need a shim for visualCaptcha, because it supports AMD.
We'll use login form for example of visualCaptcha implementation.
Login module is stored in frontend/app/modules/cs_session folder. We use frontend/app/modules/cs_session/views/login.html file to present this module in the browser window. To add visual captcha on login form we need to add next code into the login.html file:
<form id="login" ng-submit="login()">
<!-- ... -->
<!-- visualCaptcha is here -->
<div class="form-group">
<div captcha options="captchaOptions"></div>
</div>
<!-- ... -->
</form>Login module uses frontend/app/modules/cs_session/scripts/cs_login_controller.js file to define login controller. To add captchaOptions variable to the login controller we need to add the next code:
$scope.captchaOptions = {
// path to the UI images
imgPath: '/components/visualcaptcha.angular/img/',
captcha: {
// backend root url for captcha routes
url: 'http://localhost:8080/captcha',
// number of generated images
numberOfImages: 6
},
init: function ( captcha ) {
$scope.captcha = captcha;
}
};We need to fix $scope.login function in the login controller (frontend/app/modules/cs_session/scripts/cs_login_controller.js) to add proper captcha data to submission. Replace it with next code:
$scope.login = function () {
// get captcha data
var cData = $scope.captcha.getCaptchaData();
if ( !cData.valid || ! $scope.credentials ) {
$log.error('Please fill the form and solve the CAPTCHA and try again.');
} else {
//Add captcha answer to the $scope.credentials object
$scope.credentials[ cData.name ] = cData.value;
CSSessionProvider.login($scope.credentials);
}
};Login function calls CSSessionProvider.login function (frontend/app/modules/cs_session/scripts/cs_session_provider.js) that returns the promise for login service. We've updated it a bit to pass a correct captcha data to the $rootScope.$broadcast method:
login: function (credentials) {
return sessionService.login(credentials).then(function (user) {
if(user.id) {
currentUser = user;
$rootScope.$broadcast('CSSessionProvider:loginSuccess', user );
} else {
$rootScope.$broadcast('CSSessionProvider:loginFailure', user );
}
}, function( res ) {
currentUser = null;
res.data.status = res.status;
$rootScope.$broadcast('CSSessionProvider:loginFailure', res.data );
});
},And as well we've updated listener for broadcast 'CSSessionProvider:loginFailure' event in the login controller (frontend/app/modules/cs_session/scripts/cs_login_controller.js) to alert the visualCaptcha status (valid or invalid) and to refresh it:
$scope.$on('CSSessionProvider:loginFailure', function (event, data) {
$log.log('CSLoginController:', event, data);
if(data.status === '403') {
$log.error('Invalid username/password');
}
// alert the visualCaptcha status
alert( 'visualCaptcha is ' + data.captcha.status );
// We need to make sure we refresh captcha after trying to validate, to avoid abuse
$scope.captcha.refresh();
});It's a simple example with detailed code explanation how to implement visualCaptcha in node project. For the best practise visualCaptcha validation function should be used as middleware function in application routing.
All code of implementation demo is available on github.
If you run into any problems, you can look for help in visualCaptcha’s GitHub or their support page.