reCAPTCHA in action on havenlife.com

Integrating multiple reCAPTCHAs into Angular 2

liz lovero
Oct 8, 2018 · 6 min read

Putting directives, services, and observables to use. Invisible + visible reCAPTCHA.

I’m not a robot. I am told, however, that they occasionally try to sign up for life insurance.

HavenLife does not offer artificial life policies (yet), so for our recent brand relaunch we added several reCAPTCHAs to protect against spam and other automated abuse on email registration forms.

Google offers three types of reCAPTCHA:

  • Visible — requires the user to click a checkbox verifying I'm not a robot
  • Invisible — does not require a checkbox click, instead it is invoked directly when the user clicks on an existing button
  • Android — native Android reCAPTCHA from Google Play services

Design specs called for two visible reCAPTCHAs and one invisible reCAPTCHA on the new site. Adding multiple reCAPTCHAs to a single-page app adds a degree of complexity to the implementation. Integrating both visible and invisible reCAPTCHAs on an Angular 2 application proved even more challenging. In this article, we’ll go through the steps taken to add reCAPTCHA to our app and highlight a few lessons learned.

reCAPTCHA Comin’ Atcha!

reCAPTCHA + Angular2

Google maintains both the reCAPTCHA and Angular libraries and yet, like many siblings, the two don’t really get along. Angular 2 manages DOM manipulation using a modular system of components and templates. Functions on theWindow object explicitly render reCAPTCHA. Uh oh, Drama!

To get around this conflict, we used an Angular2 directive to encapsulate behavior on the Window. An Angular2 service handles communication between components and the client/server.

Disclaimer: it is best practice in Angular to not reference the DOM directly, including the Window object. In the HavenLife application we use a simple service for injecting the Window object into our component. For clarity in this article, we removed mention of this service and reference the window directly. Please note this is neither ideal nor advisable. Consider yourself warned. 👀

Client-side Implementation

recaptcha.directive.ts begins the implementation by listening for a creatively-named recaptcha selector found in HTML form templates. This directive houses Window functions to append the reCAPTCHA widget to the DOM and manages the client-side calls to Google.

In the directive, we start by declaring key and widgetId variables and importing relevant Angular libraries. key stores the site-key provided by Google upon registration and can be accessed via configs.

Pro Tips: Google test keys are available that do not require registration and will always pass — never requiring users to complete a test question. These are great for automated testing but won’t catch bugs so use with caution. For local testing, you will need to register and use your local IP address, usually 127.0.0.1, as localhost is explicitly blocked.

While other writing on the integration of Angular2 and reCAPTCHA suggest creating a global variable to hold grerecaptcha, we chose to simply declare a component-level var of type <any> . We avoided creating a global to limit exposure in the larger codebase. As always, do what works best in your context. 🤓

Please use a real function to get your own site-key out of configs

Next, to render the reCAPTCHA, ngOnInit() gets the site-key and then calls two functions addScript()and renderRecaptchaWithCallback().

addScript is straightforward. The function inserts the Javascript resource, setting the onload parameter to the name of our callback function (reCaptchaLoad)and the render parameter to explicit.

addScript also uses vanilla JS to remove any old copies of the reCAPTCHA script tag before appending to the parent. This prevents the error ReCAPTCHA placeholder element must be empty that can occur if the user encounters multiple reCAPTCHAs in a single session.

We are now ready to render our widget! renderRecaptchaWithCallback() sets up the config params (outlined in the docs) with styling parameters like size and tabindex as well as callbacks for success and reCAPTCHA expiration.

Explicit rendering of the reCAPTCHA with success and expiration callbacks.

Note: this example outlines steps to implement a visible reCAPTCHA, the invisible reCAPTCHA programmatically calls grecaptcha.execute() to invoke the challenge. More info can be found in the docs.

The next step is to define the onSuccess callback. This function will get called when all the dependencies have loaded and returns a token used to validate on the server-side. We use an Angular service to manage tokens generated by the onSuccess callback and made the token available as an Observable Subject .

reCAPTCHA tokens as observable subjects on a service

Finally, the onExpired() callback handles cases where the user completes the reCAPTCHA successfully but does not finish the email registration. recaptcha.service.ts clears the token and calls upon another component-level var instance of grecaptcha to reset the reCAPTCHA.

This retry function can also be used to handle any errors returned by the server-side validation. You will want to develop your own protocol for handling reCAPTCHA attempts and error messaging, but a service can be useful for managing this communication across the application.

Server-side Implementation

Once we pass the generated token to the server-side, we call verifyRecaptcha() to verify that the user token is on the up-and-up before going any further in the email registration process. An HTTP request client with Promise support makes for easy error handling.

The Google reCAPTCHA API returns the following response JSON object and error codes. They are not terribly forthcoming, however, about the cause of the errors for invalid-input-response which would ostensibly encompass any malicious token use.

Sample response json
Maybe all Commander Adama needed was a few reCAPTCHAs.

Invisible + Visible ReCAPTCHAs Together

Initial designs from our UX team called for 3 instances — two visible and one invisible — of reCAPTCHA. The initial purpose of addScript was to also toggle the configs and site-keys for the visible and invisible reCAPTCHAs, each of which require a separate registration with Google. Due to the slightly different client-side functions necessary to execute and render the reCAPTCHA’s we then split these out into two separate sets of directives and services. Nonetheless, on pages with two different types of reCAPTCHA we were sometimes seeing errors from the two instances of grecaptcha or the Google script tag. We addressed each edge case; the code grew bloated.

Not entirely invisible

Ultimately, it was a design and business review that determined two types of reCAPTCHA are not worth it.

Spoiler alert: the invisible reCAPTCHA isn’t entirely invisible. It requires the presence of a badge from which the followup challenge can be launched. It is possible to obscure the badge with CSS, but it is still the site of a popup in the case of a problematic user. Users found the sudden appearance of the reCAPTCHA challenge disorienting and it discouraged email registration.

Conclusion

On their own, a reCAPTCHA might not be sufficient to take down an army of determined Cylons. Rather reCAPTCHAs compose one small portion of a broad, ever-evolving defense-in-depth strategy. Perhaps a Googler will one day unite the disagreeable progeny reCAPTCHA and Angular2 into a single module. Until then, hopefully we helped.

Maybe all Commander Adama needed was a few good reCAPTCHAs

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade