3

I'm trying to integrate an Invisible reCAPTCHA with my Angular 4 form.

In the TS part of the component I have a function like this:

onSubmit(token: string) {
    alert(token);
}

And my HTML looks like this (only the relevant parts shown):

<form (ngSubmit)="onSubmit()" #myForm="ngForm">
    <button  type="submit" class="btn g-recaptcha" data-sitekey="mykey" data-callback="onSubmit">Submit</button>
</form>

When clicking on that button I get either an alert saying the token is undefined, or I get an error message like this: "Cannot contact reCAPTCHA. Check your connection and try again." Not really deterministic - sometimes I see one behavior, sometimes the other.

I thought that maybe the (ngSubmit) in the form tag could interfere with the reCAPTCHA. But if I remove (ngSubmit) then the onSubmit function is not called at all...

What is the correct way for integrating this?

There is a similar question for AngularJS here on StackOverflow, but I don't really know how to adapt that solution to Angular 4.

2

3 Answers 3

1

ReCAPTCHA may be implemented in Angular as follows. Note that this example does not include the required server-side code.

##Prerequisite

Include the reCAPTCHA api.

<script src="https://www.google.com/recaptcha/api.js" async defer></script>

##Declare ReCAPTCHA

The ReCAPTCHA variable may be declared in Typescript as follows:

declare var grecaptcha: any;

However, for more type support, the interface ReCAPTCHA may be used.

declare var grecaptcha: ReCAPTCHA;

###ReCAPTCHA Interface (v2) [recaptcha.ts]

    import { ElementRef } from '@angular/core';
    
    /**
     * Interface for Google's reCAPTCHA JavaScript API. 
     * 
     * Display API
     * @see {@link https://developers.google.com/recaptcha/docs/display}
     * 
     * Invisible API
     * @see {@link https://developers.google.com/recaptcha/docs/invisible}
     */
    export interface ReCAPTCHA {
      
      /**
       * Programatically invoke the reCAPTCHA check. Used if the invisible reCAPTCHA is on a div 
       * instead of a button.
       * 
       * @param {string} opt_widget_id Optional widget ID, defaults to the first widget created if 
       *     unspecified.
       */
      execute(opt_widget_id?: string): void;
    
      /** 
       * Renders the container as a reCAPTCHA widget and returns the ID of the newly created widget.
       * 
       * @param {ElementRef|string} container The HTML element to render the reCAPTCHA widget.  Specify 
       *    either the ID of the container (string) or the DOM element itself. 
       * @param {Object} parameters An object containing parameters as key=value pairs, for example,
       *    {"sitekey": "your_site_key", "theme": "light"}.
       */
      render(container: ElementRef|string, parameters: {[key: string]: string}): void;
    
      /** 
       * Resets the reCAPTCHA widget.
       * 
       * @param {string} opt_widget_id Optional widget ID, defaults to the first widget created if 
       *     unspecified.
       */
      reset(opt_widget_id?: string): void;
    
      /** 
       * Gets the response for the reCAPTCHA widget. Returns a null if reCaptcha is not validated. 
       * 
       * @param {string} opt_widget_id Optional widget ID, defaults to the first widget created if 
       *     unspecified.
       */
      getResponse(opt_widget_id?: string): string;
    }

<br/>

##ReCAPTCHA Angular Service [recaptcha.service.ts]

import { Injectable, OnInit } from '@angular/core'
import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams, HttpResponse } from '@angular/common/http'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
    
export interface ReCAPTCHAResponse {
  success: boolean; 
  status_code: number,
  error_codes?: Array<string>;
}

const httpOptions = {
  headers: {'Content-Type': 'application/json'},
  observe: "body",
  responseType: "json",
  withCredentials: true
}
    

@Injectable()
export class ReCAPTCHAService {
  public constructor(private http: HttpClient) {}
   
  public verifyUserResponse(userResponseToken: string): Observable<ReCAPTCHAResponse> {
    return this.http.post<ReCAPTCHAResponse>('/auth/captcha', {'g-recaptcha-response': userResponseToken}, httpOptions).
      pipe(map( (resp: HttpResponse) => { ... resp.body }))
  } 
}

##Angular Component

    import { Component, OnDestroy, OnInit } from '@angular/core'
    import { ReCAPTCHA } from './recaptcha'
    import { ReCAPTCHAResponse, ReCAPTCHAService } from './recaptcha.service'
    

    declare var grecaptcha: ReCAPTCHA;
    declare var window: any;
    
    @Component ({
      moduleId: module.id,
      selector: 'create-account',
      templateUrl: 'create-account.component.html'
    })
    export class CreateAccountComponent implements OnDestroy, OnInit {
    
      public constructor(private recaptcha: ReCAPTCHAService) {}
    
      public ngOnInit(): void {
        // Export reCAPTCHACallback to global scope.
        window['reCAPTCHACallback'] = this.reCAPTCHACallback.bind(this);
    
        grecaptcha.render('create-account-captcha', {
          'sitekey': 'your-site-key',
          'size': 'invisible',        // Optional (see docs)
          'callback': 'reCAPTCHACallback'
        });
      }
    
      /**
       * Verify reCAPTCHA response on server. 
       */
      public reCAPTCHACallback(token: string) {
        if (token == null || token.length === 0) {
          grecaptcha.reset();
          // TODO: report that captcha was invalid
        } else {
          this.recaptcha.verifyUserResponse(token).subscribe(
            (r: ReCAPTCHAResponse) => {
              if (r.success) {
                // TODO: create the new user account
              } else {
                grecaptcha.reset();
                // TODO: report that captcha was invalid
              }
            },
            (error: any) => {
              // TODO: handle server error
            }
          })
      }
    }

##Component Template

<!-- Invisible reCAPTCHA dynamically rendered. -->
<div id='create-account-captcha' class="g-recaptcha"></div>
2
  • This is very helpful and I have managed to get all the various bits working, except I am getting the error "missing-input-response". I am not using the 'invisible' option. Does this require a different implementation?
    – Farasi78
    Commented Apr 22, 2020 at 17:39
  • 1
    Okay, so for anyone else having this issue - it has to do with the back-end (server-side) request. Apparently google wants the URL as follows: https://www.google.com/recaptcha/api/siteverify?secret=' + secret + '&response=' + responseToken. Other than that this was a great answer and straightforward if you don't want to install an npm package or write a directive.
    – Farasi78
    Commented Apr 22, 2020 at 20:36
0

in component.html:

<re-captcha (resolved)="resolved($event)" siteKey="enter key"></re-captcha>

component.ts

export class SignupComponent implements OnInit {
  resolved(captchaResponse: string) {
    console.log(`Resolved captcha with response ${captchaResponse}:`);
}

first load this in module

import { RecaptchaModule } from 'ng-recaptcha';
imports: [
    BrowserModule,
     RecaptchaModule.forRoot(),  
    // RecaptchaFormsModule,  
] 

in index.html load

<script src="https://www.google.com/recaptcha/api.js" async defer></script>
1
  • The captcha response seems to be encrypted. Do you know how can we decrypt it? Commented Jun 21, 2019 at 13:44
0

Here is a great reCAPTCHA npm lib I'm using currently with Angular 10.

npm i ng-recaptcha

Not the answer you're looking for? Browse other questions tagged or ask your own question.