Skip to content

Instantly share code, notes, and snippets.

@nicosabena
Last active September 2, 2020 20:08
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save nicosabena/e2fb213c1fcf425352664d618d4361d2 to your computer and use it in GitHub Desktop.
Save nicosabena/e2fb213c1fcf425352664d618d4361d2 to your computer and use it in GitHub Desktop.
Redirect rule + webtask to do a reCaptcha after authentication
function (user, context, callback) {
// this rule requires the following configuration values:
// CAPTCHA_SECRET: a 32 bytes string that will be the shared secret between
// the rule and the webtask
// AUTH0_DOMAIN: your auth0 domain (e.g. account.auth0.com)
// CAPTCHA_REDIRECT: the URL for the webtask that will show and process CAPTCHA
// Put a specific client ID if you dont want CAPTCHA for every client
// if (context.clientID !== '[your client id]')
// return callback(null, user, context);
var secret = configuration.CAPTCHA_SECRET;
var ruleUri = 'https://' + configuration.AUTH0_DOMAIN + '/rules/captcha';
var wtUri = ruleUri + '/wt';
if (context.protocol !== "redirect-callback") {
// User has initiated a login and is forced to take a CAPTCHA challenge
// Send user's information in a JWT to avoid tampering
var token = jwt.sign({
sub: user.user_id,
clientName: context.clientName
},
secret,
{
expiresInMinutes: 5,
audience: wtUri,
issuer: ruleUri
}
);
var redirectUrl = configuration.CAPTCHA_REDIRECT;
var separator = redirectUrl.indexOf('?') !== -1 ? "&" : "?";
context.redirect = {
url: redirectUrl + separator + "token=" + token + "&webtask_no_cache=1"
};
return callback(null, user, context);
} else {
// User has been redirected to /continue?token=...,
// captcha action must be validated
// The generated token must include a `captchaOk` claim to
// confirm a successful response
var postVerify = function(err, decoded) {
if (err) {
return callback(new UnauthorizedError("Error validating token from wt: " + err));
} else if (decoded.sub !== user.user_id) {
return callback(new UnauthorizedError("Token does not match the current user."));
} else if (!decoded.captchaOk) {
return callback(new UnauthorizedError("Captcha validation was not successful.\n" +
decoded.errorMessage || ""));
} else {
// Captcha ok, go ahead with authentication
return callback(null, user, context);
}
};
jwt.verify(
context.request.query.token,
secret,
{
audience: ruleUri,
issuer: wtUri
},
postVerify
);
}
}
'use latest';
// This webtask requires the following configured secrets:
// RECAPTCHA_SITE_KEY These two values can be obtained from http://www.google.com/recaptcha/admin
// RECAPTCHA_SITE_SECRET
//
// CAPTCHA_SECRET A 32 byte string that is the shared key between the rule and the webtask
// AUTH0_DOMAIN Your Auth0 domain (e.g. account.auth0.com)
import { fromExpress } from 'webtask-tools';
const express = require('express@4.14.0');
const bodyParser = require('body-parser@1.12.4');
const app = express();
const url = require('url'); // built-in utility
const jwt = require('jsonwebtoken@5.7.0');
const request = require('request@2.67.0');
app.use(bodyParser.urlencoded()); // for parsing application/x-www-form-urlencoded
var getRuleUri = function(req) { return 'https://' + req.webtaskContext.secrets.AUTH0_DOMAIN + '/rules/captcha'; };
var getWtUri = function(req) { return getRuleUri(req) + '/wt';};
app.get('/', (req, res) => {
var captchaView = (function view() {/*
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<STYLE type="text/css">
html {
font-family: sans-serif;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
body { margin: 0; }
.container { padding-right: 15px; padding-left: 15px;margin-right: auto; margin-left: auto; }
h1 { margin: .67em 0; font-size: 2em; font-family: inherit; font-weight: 500; line-height: 1.1; color: inherit; }
</STYLE>
<title>Confirm you are human</title>
<!-- Latest compiled and minified CSS -->
<script src='https://www.google.com/recaptcha/api.js'></script>
</head>
<body>
<script type="text/javascript">
var submitform = function() {
document.getElementById("captchaform").submit();
};
</script>
<div class="container">
<form class="form-signin" action="<%= target %>" method="POST" id="captchaform">
<h1>Complete the log-in process <%= clientName %></h1>
<input type="hidden" value="<%= state %>" name="state" />
<input type="hidden" value="<%= token %>" name="token" />
<div class="g-recaptcha" data-sitekey="<%= sitekey %>" data-callback="submitform"></div>
</form>
</div>
</body>
</html>
*/}).toString().match(/[^]*\/\*([^]*)\*\/\s*\}$/)[1];
var myUri = req.originalUrl.split("?")[0];
var secrets = req.webtaskContext.secrets;
jwt.verify(
req.body.token,
secrets.CAPTCHA_SECRET, {
audience: getWtUri(req),
issuer: getRuleUri(req)
},
function(err, tokenPayload) {
var target = url.parse(req.originalUrl).pathname;
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(require('ejs').render(captchaView, {
state: req.query.state,
target: target,
token: req.query.token,
sitekey: secrets.RECAPTCHA_SITE_KEY,
clientName: tokenPayload ? "to " + tokenPayload.clientName : ""
}));
});
});
app.post('/', (req, res) => {
var secrets = req.webtaskContext.secrets;
var continueToRule = function(err, subject, state) {
var token = jwt.sign({
captchaOk: err === null,
sub: subject,
errorMessage: err
},
secrets.CAPTCHA_SECRET, {
expiresInMinutes: 2,
audience: getRuleUri(req),
issuer: getWtUri(req)
}
);
res.redirect(302, "https://" + secrets.AUTH0_DOMAIN + "/continue?state=" + req.body.state + "&token=" + token);
}
var postVerify = function(err, decoded) {
if (err) {
continueToRule("Invalid token: " + err, null);
} else if (!decoded.sub) {
continueToRule("Token does not contain 'sub' claim.", null);
} else {
continueToRule(null, decoded.sub);
}
};
var verifyCaptcha = function(captchaResponse, callback) {
request.post( {
url: 'https://www.google.com/recaptcha/api/siteverify',
form: {
secret: secrets.RECAPTCHA_SITE_SECRET,
response: captchaResponse,
remoteip: req.ip
}}, function (error, response, body) {
if (error) {
callback(error);
}
if (response.statusCode !== 200) {
callback('Error validating captcha: '+ response.statusCode);
}
var data = JSON.parse(body);
if (data.success) {
callback();
} else {
callback("Error from reCaptcha: " + JSON.stringify(data));
}
}
);
};
verifyCaptcha(req.body["g-recaptcha-response"], function(err) {
if (err) {
continueToRule("Invalid reCAPTCHA validation." + err, null);
return;
}
jwt.verify(req.body.token, secrets.CAPTCHA_SECRET, {
audience: getWtUri(req),
issuer: getRuleUri(req)
}, postVerify);
});
});
module.exports = fromExpress(app);
@micheledimeo
Copy link

Hi @nico, where can I put this code? I'd like to set a reCaptcha check before the user registration.
Is your code adequate to my case?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment