Spring Boot Angular Captcha Example

Spring Boot Angular Captcha Example thumbnail
40K
By Dhiraj 09 July, 2018

In this article, we will be integrating Google reCaptcha with a spring boot web applications with Angular as a client to protect websites from spam and abuse. The ReCaptcha will appear in an angular login form and once the form is submitted, it will be validated on the server side with the secret key along with the username and password. In the example, once the user credentials and the captcha is validated, post login page will appear.

In my previous articles, we created angular 5 example as well as angular 6 example and integrated JWT token based auth in an angular app. Hence, here we will not be demonstrating JWT integration instead we will be hardcoding the username and password in the backend code as dhiraj@devglan.com and password respectively.

We will be generating a sample angular 6 app using angular CLI and for the APIs we will be using existing Spring Boot REST APIs which we have created earlier with little modifications to integrate Google reCaptcha in the server side.

Generating Angular 6 Project

Both the CLI and generated project have dependencies that require Node 8.9 or higher, together with NPM 5.5.1 or higher for Angular 6.Angular v6 is the first release of Angular that unifies the Framework, Material and CLI. @angular/core now depends on.

  • TypeScript 2.7

  • RxJS 6.0.0

  • tslib 1.9.0

Run following command to generate angular 6 project in any location of your choice.

ng new client

Once the project is generated, you can import it in your favourite directory and get started with it. Following will be the final structure of our project. Also, you can run following commands to see angular 6 app running at localhost:4200

cd client
ng serve

The complete step by step instruction is provided in my angular 6 example here.

angular-captcha-project-strct

Google ReCaptcha SetUp

To set up Google ReCaptcha, head over to https://www.google.com/recaptcha/admin and register the site. Following is my screenshot.

google-recaptcha-register

Now, once you click on the regsiter button, you can see the site key and secret which will be used later for the integration.You can also find all the instructions on this page about integrating Google ReCaptcha with our code.In the screenshot below we can see the sample html and script tag required to place in our code for the client side integration. The secret key will be used in the server side while verifying the captcha with Google API.

google-recaptcha-client-integration

Angular Login with ReCaptcha

Following is a normal login page with an extra div element added for ReCaptcha as per the screenshot above.

login.component.html

<div class="row login">

  <div class="col-md-6 login-form">
    <h1>Login </h1>
    <form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
      <div class="form-group">
        <label for="email">Email address:</label>
        <input type="email" class="form-control" formControlName="email" id="email">
        <div *ngIf="submitted && loginForm.controls.email.errors" class="error">
          <div *ngIf="loginForm.controls.email.errors.required">Email is required</div>
        </div>
      </div>
      <div class="form-group">
        <label for="pwd">Password:</label>
        <input type="password" class="form-control" formControlName="password" id="pwd">
        <div *ngIf="submitted && loginForm.controls.password.errors" class="error">
          <div *ngIf="loginForm.controls.password.errors.required">Password is required</div>
        </div>
      </div>
      <div class="form-group">
        <div class="g-recaptcha" data-sitekey="6Lff7WIUAAAAAG2UuSYktpVi2Mz7tB6cgXnO1Tez"></div>
          <div *ngIf="captchaError" class="error">Recaptcha not verified.</div>
      </div>
      <button class="btn btn-default">Login</button>
      <div *ngIf="invalidLogin" class="error">
        <div>{{loginResponse}}</div>
      </div>
    </form>
  </div>
</div>

In the .tsfile, we have declared grecaptcha to get the response once the form is submitted. Make sure to include ReCatcha api.js in your index.html before declaring this variable. Following is the script tag that is required to include in the index.html

<script src='https://www.google.com/recaptcha/api.js'></script>
login.component.ts
import { Component, OnInit } from '@angular/core';
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {Router} from "@angular/router";
import {LoginRequest} from "../model/login.request.model";
import {UserService} from "../service/user.service";
declare var grecaptcha: any;

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  loginForm: FormGroup;
  submitted: boolean = false;
  invalidLogin: boolean = false;
  captchaError: boolean = false;
  loginResponse: string;
  constructor(private formBuilder: FormBuilder, private router: Router, private userService: UserService) { }

  onSubmit() {
    this.submitted = true;
    if (this.loginForm.invalid) {
      return;
    }
    const response = grecaptcha.getResponse();
    if (response.length === 0) {
      this.captchaError = true;
      return;
    }
    let login = new LoginRequest();
    login.email = this.loginForm.controls.email.value;
    login.password = this.loginForm.controls.password.value;
    login.recaptchaResponse = response;
    this.userService.login(login).subscribe(data => {
      if(data.status === 200) {
        //locally store the token
        this.router.navigate(['list-user']);
      } else {
        this.invalidLogin = true;
        this.loginResponse = data.message;
      }
      grecaptcha.reset();
    });
  }

  ngOnInit() {
    this.loginForm = this.formBuilder.group({
      email: ['', Validators.required],
      password: ['', Validators.required]
    });
  }
}

The response string from grecaptcha.getResponse() will be sent to server for further validation.Following is the model class.

login.request.model.ts
export class LoginRequest {

  id: number;
  firstName: string;
  lastName: string;
  email: string;
}

login.response.model.ts
export class LoginResponse {
  status : number;
  message : string;
  username : string;
  token : string;
}


Following is the implementation in our service class at Angular side.

user.service.ts
@Injectable()
export class UserService {
  constructor(private http: HttpClient) { }
  baseUrl: string = 'http://localhost:8080/users';

  login(loginRequest: LoginRequest): Observable {
    return this.http.post(this.baseUrl + '/login', loginRequest);
  }

}

Spring Boot SetUp

Spring boot team has really made spring boot environment setup easy by providing default initializers.Open the url https://start.spring.io/ and generate the project as follow.

spring-boot-project-initializers

Now unzip user-portal.zip and import into java IDE. Following will be the final structure.

google-recaptcha-server-project-strct

Spring Boot Rest APIs

Now, let us start by creating our APIs first.We have UserController where all the APIs for CRUD operation is exposed.The @CrossOrigin is used to allow Cross-Origin Resource Sharing (CORS) so that our angular application running on different server can consume these APIs from a browser. For simplicity I am only including the login API here.

In real time application, we will have an AuthController for performing login. There are many articles posted on Devglan for login auth here.

UserController.java
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping({"/users"})
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public LoginResponse login(@RequestBody LoginRequest loginRequest){
        return userService.login(loginRequest);
    }
	
	...
	...
	
}
LoginRequest
public class LoginRequest {

    private String email;
    private String password;
    private String recaptchaResponse;
	
	Getters and setters
	
}

Following is the service class implementation that will actually make DB calls and validate the user credentials along with the captcha. To make the example simple, I have hardcoded the username and password.

UserServiceImpl.java
    @Override
    public LoginResponse login(LoginRequest loginRequest) {
        LoginResponse loginResponse = new LoginResponse();
        boolean captchaVerified = captchaService.verify(loginRequest.getRecaptchaResponse());
        if(!captchaVerified) {
            loginResponse.setMessage("Invalid captcha");
            loginResponse.setStatus(400);
        }
        if(captchaVerified && loginRequest.getEmail().equals("dhiraj@devglan.com") && loginRequest.getPassword().equals("password")) {
            loginResponse.setMessage("Success");
            loginResponse.setStatus(200);
            loginResponse.setUsername("dhiraj");
            loginResponse.setToken("token");
        }else {
            loginResponse.setMessage("Invalid credentials.");
            loginResponse.setStatus(400);
        }
        return loginResponse;
    }

Here, captchaService is injected as a dependency which will validate the captcha response coming from the client and return either true or false after validation. Following is the screenshot of the Google API response.

google-recaptcha-api-response LoginResponse.java
public class LoginResponse {

    private int status;
    private String message;
    private String username;
    private String token;
	
	Getters and setters
	
}

Google Recaptcha Server Side Integration

Below is the implementation of CaptchaService.java that will make a POST request to https://www.google.com/recaptcha/api/siteverify in order to validate the captcha response coming from the client side.

CaptchaService.java
package com.devglan.userportal.service;

import com.devglan.userportal.RecaptchaResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

@PropertySource("classpath:application.properties")
@Service
public class CaptchaService {

    private final RestTemplate restTemplate;

    public CaptchaService(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder.build();
    }

    @Value("${google.recaptcha.secret.key}")
    public String recaptchaSecret;
    @Value("${google.recaptcha.verify.url}")
    public String recaptchaVerifyUrl;

    public boolean verify(String response) {
        MultiValueMap param= new LinkedMultiValueMap<>();
        param.add("secret", recaptchaSecret);
        param.add("response", response);

        RecaptchaResponse recaptchaResponse = null;
        try {
            recaptchaResponse = this.restTemplate.postForObject(recaptchaVerifyUrl, param, RecaptchaResponse.class);
        }catch(RestClientException e){
            System.out.print(e.getMessage());
        }
       if(recaptchaResponse.isSuccess()){
            return true;
        }else {
            return false;
        }
    }
}

Following entries are required in application.properties file that we get from Google ReCatcha during registration.

google.recaptcha.secret.key=6Lff7WIUAAAAAP_MkVOaQlxvrUGNtEC_OLIA6s5L
google.recaptcha.verify.url=https://www.google.com/recaptcha/api/siteverify

Adding CORS with Spring Boot Angular

Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin. Since, we have server running at localhost:8080 and angular running at localhost:4200, we see error as No 'Access-Control-Allow-Origin'. To avoid this error, we can add below filter in the spring project.

package com.devglan.config;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;


public class CORSFilter implements Filter {

	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
		System.out.println("Filtering on...........................................................");
		HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
		response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
		response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization, Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers");

		chain.doFilter(req, res);
	}

	public void init(FilterConfig filterConfig) {}

	public void destroy() {}

}

Testing Google ReCaptcha App

Now, we are all set to test our captcha implementation. For this let us first run our angular app with ng serve and hit localhost:4200

google-recaptcha-user-login

Once the login is successfull,you can see the list page.

google-recaptcha-post-login

Conclusion

In this article we discussed about integrating Google ReCaptcha with a spring boot angular app. The complete source code can be found on my Github page here. If you have any suggestions, please let me know in the comments below:

Share

If You Appreciate This, You Can Consider:

We are thankful for your never ending support.

About The Author

author-image
A technology savvy professional with an exceptional capacity to analyze, solve problems and multi-task. Technical expertise in highly scalable distributed systems, self-healing systems, and service-oriented architecture. Technical Skills: Java/J2EE, Spring, Hibernate, Reactive Programming, Microservices, Hystrix, Rest APIs, Java 8, Kafka, Kibana, Elasticsearch, etc.

Further Reading on Angular JS