Contents


Add Google reCAPTCHA to your IBM Cloud Node.js application

Ensure humans are in the loop and avoid denial of service attacks

Comments

In this article, you learn how to access the Google reCAPTCHA service from your IBM Cloud Node.js application. Doing so allows you to ensure that the user of the application is human and not just an automated system. This requirement prevents many attacks that rely on sending numerous requests from such a system.

What you'll need to build your application

  • An IBM Cloud account. (Try IBM Cloud for free.)
  • A Facebook account.
  • A Google account.
  • Knowledge of HTML and JavaScript.
  • Knowledge of the MEAN application stack (at least Node.js and Express).
  • A development environment that can upload a Node.js application to IBM Cloud, such as Eclipse.

Run the appGet the code

Some attacks use automated systems to generate large numbers of requests. In this article, you learn how to use the Google reCAPTCHA service to ensure there is a human in the loop.

Why use reCAPTCHA?

There are a number of attacks that rely on getting the targeted system to respond to a large number of requests. Writing a program to issue such requests is relatively simple and cheap. However, with reCAPTCHA (or any other CAPTCHA variant), requests have to include the solution for a problem that is very easy for humans, but very difficult for computers. This turns automated attacks that are very cheap into attacks that require a large amount of boring, expensive, human interaction.

Spam

Any service that allows users to interact with each other can be used to send annoying advertising, also known as spam. Most administrators respond to accusations of spamming by deleting the offending account. From the spammer's perspective, one solution is to create a huge number of disposable accounts.

If account registration requires human interaction, however, creating those disposable accounts is a lot harder and more expensive. This typically leads spammers to look for easier victims.

Denial of service attacks

Denial of service attacks can be petty vandalism. They can also be part of a more serious attack, when used to force people to achieve their work through a different system that is less secure. Most denial of service attacks are based on simply sending out a huge number of requests and overwhelming the system. This is unlikely to happen with IBM Cloud's infrastructure, but a large number of requests could increase the cost of the application.

But if making a request requires a human being, sending a huge number of nonsense requests is no longer economically feasible.

Password enumeration

One way to break passwords is to try all possible values in order of likelihood (start with dictionary words, continue with dictionary words that have "o" replaced by zero, and so on). A common solution is to block further attempts after a number of wrong passwords are entered, but this gives attackers an easy denial of service attack against accounts. They can always get accounts locked by entering a small number of passwords.

By requiring a human in the loop, either immediately or after a certain number of failed password attempts, you make password enumeration too cumbersome to be worth doing.

How to use reCAPTCHA

To use reCAPTCHA, you need to activate it on Google's website and then modify your program to call it.

Configuration on the Google side

Register your application with Google's reCAPTCHA service at https://www.google.com/recaptcha/admin#list. To avoid problems, register your full domain name. For example, for my application I registered recaptcha.mybluemix.net.

Google provides you with two keys: a site key to use in your HTML, and a that is used for your application on the server to communicate with Google.

Configuration on the client side

To show the user the reCAPTCHA prompt within a form, follow these steps:

  1. Somewhere in the HEAD tag of the HTML, call the Google reCAPTCHA script.
    <script src='https://www.google.com/recaptcha/api.js'></script>
  2. Identify the form to protect, the one that should be submitted only by humans. Add the definition below at the bottom of that form by using the site key you got from Google.
	<div class="g-recaptcha" data-sitekey="--- your site key goes here –--"></div>

To see an example, go to http://recaptcha.mybluemix.net/index.html and view the source.

Configuration on the server side

To use reCAPTCHA, follow these steps to modify the application running on the server (app.js):

  1. Add the body-parser and https packages to package.json as dependencies. You need the first to read the results of a POST form, and the second to send a request to Google's servers to interpret the result.
  2. Add the following lines to apply the body parser package:
    // Use body-parser to receive POST form requests and JSON
    var bodyParser = require("body-parser");
    app.use(bodyParser.urlencoded({extended:true}));
  3. Add code that sends an HTTPS request to Google, as explained in the article "Manage security alerts with IBM DevOps Track & Plan."
    1. Create a variable to use the HTTPS package.
    2. Issue a request to Google's servers in the handler for the form.
      		// An attempt to log on
      		app.post("/login", function(req, res) {
      		.
      		.
      		.
      		
      		  var httpsReq = https.request(  <<URL goes here >>,
      			function(httpsRes) {
      		.
      		.
      		.
      		
      		  });
      		 
      		  httpsReq.on("error", function(err) {
      		  	res.send("Error: " + JSON.stringify(err));
      		  });
      		  
      		  httpsReq.end();
      		});
  4. When sending the request, add two query parameters to the URL: secret, which contains the secret key, and response, which contains the value returned by the reCAPTCHA code in the browser. That value is provided in the body for a POST.
    	  var httpsReq = https.request("https://www.google.com/recaptcha/api/siteverify" +
    	  	"?secret=<<your secret goes here>>" +
    		"&response=" + req.body["g-recaptcha-response"],
  5. The response might come in multiple chunks, so you need to gather them until the response is done.
    var googleResponse = "";
    	.
    	.
    	.
    	  	httpsRes.on("data", function (chunk) {
    			googleResponse += chunk;
      		});
  6. The response is a JSON object. You can use JSON.parse() to parse it. The parameter that tells you if the user is known to be human is success. It is a Boolean.
    		httpsRes.on("end", function() {
    		  var human = JSON.parse(googleResponse).success;
    		  res.send(human);
    		 });

To check that the reCAPTCHA service works as expected, submit once and see that the result is true. Then wait a few seconds and reload, confirming the form resubmission. Reusing the g-recaptcha-response value is something that happens when requests come from an automated system. A programmer trying to attack the application could manually create a valid value once, and then program the attack to send that value automatically with every submission. To prevent such an attack, the response from Google to a previously used value is false.

Combine reCAPTCHA with the IBM Cloud single sign-on service

IBM Cloud has a single sign-on service. For a specific application, we might want to use this service, but we do not trust all identity sources equally. We are afraid that some identity sources are susceptible to automated attacks, and therefore we want to require a reCAPTCHA when they are used. But we do not want to do it for the identity sources we trust.

Configure the single sign-on service

These are the steps to configure the single sign-on service:

  1. In the IBM Cloud dashboard, click USE SERVICES OR APIS.
  2. Select Security > Single Sign On.
  3. Click CREATE.
  4. Specify a name. For my application, I chose recaptcha-sso.
  5. If you get an error, ignore it. Just go back to the dashboard and click on the service.
  6. Click SETUP.
  7. You are presented with a list of identity sources. For the purposes of this article, configure two of them: Facebook and Google+.

Facebook

  1. Click the Facebook tile.
  2. Copy the OAUTH redirect URI. It looks similar to:
    https://recaptcha-sso-aj5bgntzl9-ck11.iam.ibmcloud.com/idaas/mtfim/sps/idaas/login/facebook/callback
  3. Log in to Facebook.
  4. Go to the Facebook developer page and click My Apps > Add a New App.
  5. Click Website.
  6. Name the application and click Create New Facebook App ID.
  7. Select a category and click Create App ID.
  8. Click Skip Quickstart.
  9. Click Settings.
  10. Click Add Platform.
  11. Click Website.
  12. Type the URL for your application.Screen capture showing app URL
    Screen capture showing app URL
  13. Click the Advanced tab.
  14. Paste the OAUTH redirect URI in the Valid OAuth redirect URIs field.
  15. Click Save Changes.
  16. Click the Basic tab.
  17. Copy the App ID and App Secret from Facebook into IBM Cloud. You will need to click Show to see the app secret.
  18. Click Save.
  19. Click Verify and then Click here.
  20. In Facebook, click Okay.
  21. When you see a message that the login was successful, return to IBM Cloud and click Done.

Google +

  1. Click the Google + tile.
  2. Copy the OAUTH redirect URI. It looks similar to:
    https://recaptcha-sso-aj5bgntzl9-ck11.iam.ibmcloud.com/idaas/mtfim/sps/idaas/login/google/callback
  3. Click Click here. You are redirected to the Google Developers Console.
  4. In the Google Developers Console, click Enable and manage APIs. Screen capture showing Google console
    Screen capture showing Google console
  5. Click Google+ API.
  6. Click Enable API.
  7. Click Go to Credentials.
  8. Select to call the API from a Web server.
  9. Select User data and click What credentials do I need?.
  10. Type the authorized redirect URI you got, and click Create client ID.
  11. Type a name for your application and click Continue.
  12. Download client_id.json. Copy the client ID and client secret and paste them into IBM Cloud.
  13. Click Save.

Use the identity service in the application

To use the identity service, follow these steps:

  1. Return to your application.
  2. Click the BIND A SERVICE OR API tile.
  3. Select the single sign-on service and click ADD.
  4. Click Restage.
  5. Open the service again, and click the new INTEGRATE tab.
  6. Type a callback URL. The URL for your application followed by /sso is a reasonable choice.
    https://recaptcha.mybluemix.net/sso
  7. Click Save and then OK.
  8. In package.json, add these dependencies:
    "dependencies": {
        .
        .
        .
        "passport": "*",
        "cookie-parser": "*",
        "express-session": "*",
    	    "passport-idaas-openidconnect": "*"
    	}
  9. Add these lines to app.js, after the code that defined the app and appEnv variables:
    var passport = require('passport');
    var cookieParser = require('cookie-parser');
    var session = require('express-session');
    
    app.use(cookieParser());
    app.use(session({resave: 'true', saveUninitialized: 'true' , secret: 'keyboard cat'}));
    app.use(passport.initialize());
    app.use(passport.session());
    
    passport.serializeUser(function(user, done) {
       done(null, user);
    });
    
    passport.deserializeUser(function(obj, done) {
       done(null, obj);
    });         
    
    // VCAP_SERVICES contains all the credentials of services bound to
    // this application. For details of its content, please refer to
    // the document or sample of each service.  
    var services = JSON.parse(process.env.VCAP_SERVICES || "{}");
    var ssoConfig = services.SingleSignOn[0];
    var client_id = ssoConfig.credentials.clientId;
    var client_secret = ssoConfig.credentials.secret;
    var authorization_url = ssoConfig.credentials.authorizationEndpointUrl;
    var token_url = ssoConfig.credentials.tokenEndpointUrl;
    var issuer_id = ssoConfig.credentials.issuerIdentifier;
    var callback_url = appEnv.url + "/sso";        
    
    var OpenIDConnectStrategy = require('passport-idaas-openidconnect').IDaaSOIDCStrategy;
    var Strategy = new OpenIDConnectStrategy({
                     authorizationURL : authorization_url,
                     tokenURL : token_url,
                     clientID : client_id,
                     scope: 'openid',
                     response_type: 'code',
                     clientSecret : client_secret,
                     callbackURL : callback_url,
                     skipUserProfile: true,
                     issuer: issuer_id},
    	function(iss, sub, profile, accessToken, refreshToken, params, done)  {
    	         	process.nextTick(function() {
    		profile.accessToken = accessToken;
    		profile.refreshToken = refreshToken;
    		done(null, profile);
             	})
    });
    
    passport.use(Strategy);
    app.get('/sso-login', passport.authenticate('openidconnect', {}));
  10. Add the code to require authentication using the ensureAuthenticated() function as middleware. In this application, I chose to put all the URLs that require authentication under /auth.
    function ensureAuthenticated(req, res, next) {
    	if(!req.isAuthenticated()) {
    	          	req.session.originalUrl = req.originalUrl;
    		res.redirect('/sso-login');
    	} else {
    		return next();
    	}
    }
    
    app.use("/auth/*", ensureAuthenticated);
  11. Run the application.
  12. Try to access an authenticated path such as /auth/test to log in. Notice that you can log in, but then you are redirected to /sso on your application, which does not have a handler yet.

Read identity attributes

  1. Create a handler for /sso.
    app.get('/sso',function(req,res,next) {               
                 var redirect_url = req.session.originalUrl;                
                 passport.authenticate('openidconnect', {
                         successRedirect: redirect_url,                                
                         failureRedirect: '/failure',                        
              })(req,res,next);
            });
  2. Create a handler for failure to log in.
    app.get('/failure', function(req, res) {
                 res.send('login failed');
    });
  3. Create a path under /auth that displays the user information. Session information, such as the user identity, is available at req.session.
    app.get('/auth/whoami', function(req, res) {
    	res.send(JSON.stringify(req.session));
    });

    To see the user information handler in the sample application, click here.
    Note: In a production application you would delete the /auth/whoami handler to avoid disclosing information. I kept it in the article's application for demonstration purposes.
  4. Use a JSON-to-HTML translator, such as http://json.bloople.net/, to interpret the result. These are the most important fields:
    • passport.user.id: A unique user identifier
    • passport.user._json.realmName: The source of the user
    • passport.user._json.displayName: The user's name

Send the reCAPTCHA only when required

Finally, create a welcome page. I find it easiest to have pages that are mostly static and provide all the specific parameters in a separate JavaScript "file."

  1. Modify the ensureAuthenticated handler to redirect to a different page if the user is authenticated, but the realmName is www.facebook.com (meaning that you require reCAPTCHA) and recaptcha is not true. The new lines are 6 through 10.
    function ensureAuthenticated(req, res, next) {
    	if(!req.isAuthenticated()) {
    	    req.session.originalUrl = req.originalUrl;
    		res.redirect('/login-sso');
    	} else {
    		if ((req.session.passport.user._json.realmName === "www.facebook.com") &&
    			!req.session.recaptcha) {
    			res.session.orginalUrl = req.originalUrl;
    			res.redirect("/get-recaptcha.html");			
    		} else
    			return next();
    	}
    }
  2. Create a get-recaptcha.html file that asks only for a reCAPTCHA. In the reCAPTCHA div, use the data-callback attribute to call a function when Google decides the user is a human being.
        <form action="recaptcha-res" id="recap" role="form" method="post" class="form-inline">
    		<div class="g-recaptcha"
    			data-sitekey="6LccjhcTAAAAAG-BsW-f0v9P91oWiin2sZHfxSaK"
    			data-callback="verified"
    		>
    		</div>
        </form>

    The called function submits the form, sending the reCAPTCHA result to the server.
    <script>
        var verified = function() {
    	document.getElementById("recap").submit();
        };
    </script>
  3. Create a handler for the reCAPTCHA result. This handler is very similar to the one shown earlier, except that it redirects to the original URL and sets a session variable so that reCAPTCHA won't be required again during this session.
      		httpsRes.on("end", function() {
      			var human = JSON.parse(googleResponse).success;
      			if (human) {
      				// Set the reCAPTCHA value to true
      				req.session.recaptcha = true;
      				
      				// Redirect to where the user wanted to go
      				res.redirect(req.session.originalUrl);
      			}  				
      		});

Serve the authenticated part of the application

  1. Create in your application a new directory, for example /auth, for static files that require authentication.
  2. Add a line after the line that calls ensureAuthenticated() to serve the static files that require authentication. The new line is line 3.
    // Serve files that require authentication, only if authenticated
    app.use("/auth/*", ensureAuthenticated);
    app.use("/auth", express.static(__dirname + '/auth'));
  3. Create a handler for /auth/session.js that returns the session data that might be useful in the browser.
    app.get("/auth/session.js", function(req, res) {
    	res.send("// User data\n" +
    		"var displayName = '" + req.session.passport.user._json.displayName + "';\n" +
    		"var realmName = '" + req.session.passport.user._json.realmName + "';\n"
    	);
    });
  4. Use the script in your HTML files as needed. For example:
        <script src="session.js"></script>
    
        
    	<script>
    	var myApp = angular.module("myApp", []);
    	var scope;
    		
    
    	myApp.controller("myCtrl", function($scope) {
    		// Make the scope available outside the controller, which is very useful for
    		// debugging
      		scope = $scope;
      		
      		// Provide the session parameters in the scope
      		$scope.displayName = displayName;
      		$scope.realmName = realmName;
      	});
        
        </script>
        
      </head>
    
      <body>
      
      	<h2>Hello {{displayName}}</h2>
      	From {{realmName}}    
    
      </body>

Conclusion

You should now be able to use reCAPTCHA in your IBM Cloud Node.js applications, with or without the IBM Cloud single sign-on service. Note that before releasing an application using the single sign-on service to production, you have to do a few more steps:

  1. Change keyboard cat to a different string that would actually be secret.
  2. Require the use of HTTPS.
  3. Store session information somewhere better than memory, in case you have multiple instances that use them and to avoid memory leaks.

Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Security
ArticleID=1027836
ArticleTitle=Add Google reCAPTCHA to your IBM Cloud Node.js application
publish-date=03032016