-
Notifications
You must be signed in to change notification settings - Fork 571
Using reCAPTCHA
The goal of this page is to explain the steps you can take to integrate Google's reCAPTCHA
service using the npm/re-captcha
module.
reCAPTCHA is a free CAPTCHA service that protects your site against spam, malicious registrations and other forms of attacks where computers try to disguise themselves as a human; a CAPTCHA is a Completely Automated Public Turing test to tell Computers and Human Apart. reCAPTCHA comes in the form of a widget that you can easily add to your blog, forum, registration form, etc.
https://developers.google.com/recaptcha/
recaptcha renders and verifies Recaptcha captchas.
https://npmjs.org/package/re-captcha
A fellow GitHub friend and Drywall user opened a feature request to optionally include a reCAPTCHA widget for the signup flow. This might be good for some projects to prevent bot spam. Although it's not baked into Drywall, it's pretty simple to add to any form. The following guide will step you through adding a reCAPTCHA widget to the contact form of Drywall.
$ npm install re-captcha --save
You need to get API keys (public and private) for the reCAPTCHA service from Google. If you're just testing this out on your local machine, it's probably best to check the global key option Enable this key on all domains (global key)
. Obviously in a production environment it makes sense to limit this to your domain.
After you have your API keys add them to your Drywall /config.js
file.
exports.recaptcha = {
key: 'xxxxx-xxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxx',
secret: 'xxxxx-xxxxxxxxxxxxxxxxxxxxxxxxx_xxxxxxxx'
};
Then pull these variables into our app in /app.js
so we can access them in our view logic.
//recaptcha settings
app.set('recaptcha-key', config.recaptcha.key);
app.set('recaptcha-secret', config.recaptcha.secret);
Inside of /views/contact/index.js
we're going to update the init
function and include our public reCAPTCHA key as a local variable so we can access it in our markup to be sent to the client.
exports.init = function(req, res){
res.render('contact/index', {
recaptchaKey: req.app.get('recaptcha-key')
});
};
Now we're going to update our sendMessage
function by creating an instance of the Recaptcha
module and adding a step to our workflow to validate the reCAPTCHA challenge workflow.on('validateRecaptcha'...
before validating other form variables workflow.on('validate'...
.
exports.sendMessage = function(req, res){
var workflow = req.app.utility.workflow(req, res);
var Recaptcha = require('re-captcha');
var recaptcha = new Recaptcha(req.app.get('recaptcha-key'), req.app.get('recaptcha-secret'));
workflow.on('validateRecaptcha', function() {
var data = {
remoteip: req.connection.remoteAddress,
challenge: req.body.recaptcha_challenge_field,
response: req.body.recaptcha_response_field
};
recaptcha.verify(data, function(err) {
if (err) {
workflow.outcome.errfor.recaptcha = 'failed';
return workflow.emit('response');
}
else {
workflow.emit('validate');
}
});
});
workflow.on('validate', function() {
if (!req.body.name) {
workflow.outcome.errfor.name = 'required';
}
if (!req.body.email) {
workflow.outcome.errfor.email = 'required';
}
if (!req.body.message) {
workflow.outcome.errfor.message = 'required';
}
if (workflow.hasErrors()) {
return workflow.emit('response');
}
workflow.emit('sendEmail');
});
workflow.on('sendEmail', function() {
req.app.utility.sendmail(req, res, {
from: req.app.get('smtp-from-name') +' <'+ req.app.get('smtp-from-address') +'>',
replyTo: req.body.email,
to: req.app.get('system-email'),
subject: req.app.get('project-name') +' contact form',
textPath: 'contact/email-text',
htmlPath: 'contact/email-html',
locals: {
name: req.body.name,
email: req.body.email,
message: req.body.message,
projectName: req.app.get('project-name')
},
success: function(message) {
workflow.emit('response');
},
error: function(err) {
workflow.outcome.errors.push('Error Sending: '+ err);
workflow.emit('response');
}
});
});
workflow.emit('validateRecaptcha');
};
Note: we're not using the "rendering" features of npm/re-captcha
. This is because we're using client side templating via underscore. We're primarily using it for the "verification" feature.
Now we're going to update our Jade template /views/contact/index.jade
to include Google's reCAPTCHA ajax script, pull in our public API key and add other needed markup.
extends ../../layouts/default
block head
title Contact Us
block neck
link(rel='stylesheet', href='/views/contact/index.min.css?#{cacheBreaker}')
block feet
script(src='http://www.google.com/recaptcha/api/js/recaptcha_ajax.js')
script(src='/views/contact/index.min.js?#{cacheBreaker}')
block body
div.row
div.col-sm-6
div.page-header
h1 Send A Message
div
div#contact
div.col-sm-6.special
div.page-header
h1 Contact Us
p.lead Freddy can't wait to hear from you.
i.icon-reply-all.super-awesome
address 1428 Elm Street • San Francisco, CA 94122
script(type='text/template', id='tmpl-contact')
form
div.alerts
|<% _.each(errors, function(err) { %>
div.alert.alert-danger.alert-dismissable
button.close(type='button', data-dismiss='alert') ×
|<%= err %>
|<% }); %>
|<% if (success) { %>
div.alert.alert-info.alert-dismissable
button.close(type='button', data-dismiss='alert') ×
| We have received your message. Thank you.
|<% } %>
|<% if (!success) { %>
div.form-group(class!='<%= errfor.recaptcha ? "has-error" : "" %>')
label.control-label Recaptcha:
div#recaptcha ...loading...
span.help-block <%= errfor.recaptcha %>
div.form-group(class!='<%= errfor.name ? "has-error" : "" %>')
label.control-label Your Name:
input.form-control(type='text', name='name', value!='<%= name %>')
span.help-block <%= errfor.name %>
div.form-group(class!='<%= errfor.email ? "has-error" : "" %>')
label.control-label Your Email:
input.form-control(type='text', name='email', value!='<%= email %>')
span.help-block <%= errfor.email %>
div.form-group(class!='<%= errfor.message ? "has-error" : "" %>')
label.control-label Message:
textarea.form-control(name='message', rows='5') <%= message %>
span.help-block <%= errfor.message %>
div.form-group
button.btn.btn-primary.btn-contact(type='button') Send Message
|<% } %>
script(type='text/template', id='recaptcha-key') #{recaptchaKey}
On the client side /public/views/contact/index.js
we add the Recaptcha
variable to the jsHint comment at the top so the linter doesn't cry. We also update the render function for the form view to generate the reCAPTCHA widget only if the success
variable is false. And finally in the contact
method of the view we're going to include the challenge and response variables from the reCAPTCHA widget so they can be validated on the server.
/* global app:true, Recaptcha:false */
(function() {
'use strict';
app = app || {};
app.Contact = Backbone.Model.extend({
url: '/contact/',
defaults: {
success: false,
errors: [],
errfor: {},
name: '',
email: '',
message: ''
}
});
app.ContactView = Backbone.View.extend({
el: '#contact',
template: _.template( $('#tmpl-contact').html() ),
events: {
'submit form': 'preventSubmit',
'click .btn-contact': 'contact'
},
initialize: function() {
this.model = new app.Contact();
this.listenTo(this.model, 'sync', this.render);
this.render();
},
render: function() {
this.$el.html(this.template( this.model.attributes ));
this.$el.find('[name="name"]').focus();
if (!this.model.get('success')) {
Recaptcha.create($('#recaptcha-key').text(),
'recaptcha', {
theme: "red",
callback: Recaptcha.focus_response_field
}
);
}
},
preventSubmit: function(event) {
event.preventDefault();
},
contact: function() {
this.$el.find('.btn-contact').attr('disabled', true);
this.model.save({
name: this.$el.find('[name="name"]').val(),
email: this.$el.find('[name="email"]').val(),
message: this.$el.find('[name="message"]').val(),
recaptcha_challenge_field: Recaptcha.get_challenge(),
recaptcha_response_field: Recaptcha.get_response()
});
Recaptcha.destroy();
}
});
$(document).ready(function() {
app.contactView = new app.ContactView();
});
}());
I hope this was helpful. If you have questions or think this page should be expanded please contribute by opening an issue or updating this page.