Implementing google 2-FA authenticator in Spring Boot

                            Welcome geeks to my brand new blog. Today we are going to perform a basic POC, in which we will set up a basic google 2FA authentication in spring boot.


    Before actually jumping into implementation let's try to understand what is 2FA and why we need this. So Basically 2FA stands for Two Factor Authentication. It's a randomly generated time-based password for accessing web apps.

''2FA, or two-factor authentication, is an identity verification method that requires a user to provide a second authentication factor in addition to a password or two authentication factors instead of a password in order to access a website, application or network.''

See the below diagram for a better understanding of how it works behind the scene.


        There are multiple vendors in the market who provides such services as Google, and Microsoft. To be precise we are using google base 2-FA implementation today. 
            
        To leverage this service we need to set up it before use. Users have to scan the randomly generated QR code from the application then it will start randomly generating time base OTP that we can use for validation users via their SDKs.


Getting Started:


Tech Stack we are using for this POC is
  •  Java 1.8 with Spring Boot 2. X
  •  Maven
  •  Some 3rd party dependencies like Warrenstrange's googleauth library for authentication of TOTP and Google's zxing for generating QR codes.
  • Swagger for API documentation(not necessary)
So as a very first step initialize an empty spring boot web starter project and after adding all dependencies the final pom will look like below.


file name: pom.xml
 <?xml version="1.0" encoding="UTF-8"?>  
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
   <modelVersion>4.0.0</modelVersion>  
   <parent>  
     <groupId>org.springframework.boot</groupId>  
     <artifactId>spring-boot-starter-parent</artifactId>  
     <version>2.1.4.RELEASE</version>  
     <relativePath/> <!-- lookup parent from repository -->  
   </parent>  
   <groupId>com.vladm</groupId>  
   <artifactId>google-auth-demo</artifactId>  
   <version>0.0.1-SNAPSHOT</version>  
   <name>Google-2FA-Demo</name>  
   <description>Google 2FA Authenticator Integration In Spring Boot</description>  
   <properties>  
     <java.version>1.8</java.version>  
     <swagger.version>2.9.2</swagger.version>  
   </properties>  
   <dependencies>  
     <dependency>  
       <groupId>org.springframework.boot</groupId>  
       <artifactId>spring-boot-starter-web</artifactId>  
     </dependency>  
     <dependency>  
       <groupId>com.warrenstrange</groupId>  
       <artifactId>googleauth</artifactId>  
       <version>1.4.0</version>  
     </dependency>  
     <dependency>  
       <groupId>com.google.zxing</groupId>  
       <artifactId>core</artifactId>  
       <version>3.3.0</version>  
     </dependency>  
     <dependency>  
       <groupId>com.google.zxing</groupId>  
       <artifactId>javase</artifactId>  
       <version>3.3.0</version>  
     </dependency>  
     <dependency>  
       <groupId>io.springfox</groupId>  
       <artifactId>springfox-swagger2</artifactId>  
       <version>${swagger.version}</version>  
     </dependency>  
     <dependency>  
       <groupId>io.springfox</groupId>  
       <artifactId>springfox-swagger-ui</artifactId>  
       <version>${swagger.version}</version>  
       <scope>compile</scope>  
     </dependency>  
     <dependency>  
       <groupId>org.projectlombok</groupId>  
       <artifactId>lombok</artifactId>  
       <optional>true</optional>  
     </dependency>  
     <dependency>  
       <groupId>org.springframework.boot</groupId>  
       <artifactId>spring-boot-starter-test</artifactId>  
       <scope>test</scope>  
     </dependency>  
   </dependencies>  
   <build>  
     <plugins>  
       <plugin>  
         <groupId>org.springframework.boot</groupId>  
         <artifactId>spring-boot-maven-plugin</artifactId>  
       </plugin>  
     </plugins>  
   </build>  
 </project>  
Now generate a couple of DTOs for TOTP  creation and validation.

file name: UserTOTP.java
 package com.techreloded;  
 import lombok.AllArgsConstructor;  
 import lombok.Data;  
 import lombok.NoArgsConstructor;  
 import java.util.List;  
 @Data  
 @NoArgsConstructor  
 @AllArgsConstructor  
 class UserTOTP {  
   private String username;  
   private String secretKey;  
   private int validationCode;  
   private List<Integer> scratchCodes;  
 }  
file name: ValidateCodeDto.java
 package com.techreloded;  
 import lombok.AllArgsConstructor;  
 import lombok.Data;  
 import lombok.NoArgsConstructor;  
 @Data  
 @NoArgsConstructor  
 @AllArgsConstructor  
 class ValidateCodeDto {  
   private Integer code;  
   private String username;  
 }  
file name: Validation.java
 package com.techreloded;  
 import lombok.AllArgsConstructor;  
 import lombok.Data;  
 import lombok.NoArgsConstructor;  
 @Data  
 @NoArgsConstructor  
 @AllArgsConstructor  
 class Validation {  
   private boolean valid;  
 }    
            Below is the Swagger configuration file for API documentation, you can ignore this entirely. This is not necessary for this POC but it will add documentation automatically for your service.

file name: SwaggerConfig.java
 package com.techreloded;  
 import lombok.RequiredArgsConstructor;  
 import org.springframework.context.annotation.Bean;  
 import org.springframework.context.annotation.Configuration;  
 import springfox.documentation.builders.PathSelectors;  
 import springfox.documentation.builders.RequestHandlerSelectors;  
 import springfox.documentation.spi.DocumentationType;  
 import springfox.documentation.spring.web.plugins.Docket;  
 import springfox.documentation.swagger2.annotations.EnableSwagger2;  
 @Configuration  
 @EnableSwagger2  
 @RequiredArgsConstructor  
 public class SwaggerConfig {  
   @Bean  
   public Docket api() {  
     return new Docket(DocumentationType.SWAGGER_2)  
         .select()  
         .apis(RequestHandlerSelectors.any())  
         .paths(PathSelectors.any())  
         .build();  
   }  
 }  
Next, we need to set up our custom authenticator implementation.

file name: CustomGoogleAuthenticatorConfig.java
 package com.techreloded;  
 import com.warrenstrange.googleauth.GoogleAuthenticator;  
 import lombok.RequiredArgsConstructor;  
 import org.springframework.context.annotation.Bean;  
 import org.springframework.context.annotation.Configuration;  
 @Configuration  
 @RequiredArgsConstructor  
 public class CustomGoogleAuthenticatorConfig {  
   private final CredentialRepository credentialRepository;  
   @Bean  
   public GoogleAuthenticator gAuth() {  
     GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();  
     googleAuthenticator.setCredentialRepository(credentialRepository);  
     return googleAuthenticator;  
   }  
 }  

Now create the credential repository as below.

file name: CredentialRepository.java
 package com.techreloded;  
 import com.warrenstrange.googleauth.ICredentialRepository;  
 import lombok.AllArgsConstructor;  
 import lombok.Data;  
 import lombok.NoArgsConstructor;  
 import org.springframework.stereotype.Component;  
 import java.util.HashMap;  
 import java.util.List;  
 import java.util.Map;  
 @Component  
 public class CredentialRepository implements ICredentialRepository {  
   private final Map<String, UserTOTP> usersKeys = new HashMap<String, UserTOTP>() {{  
     put("gaurav.mute@gmail.com", null);  
     put("gaurav.kumar3@publicissapient.com", null);  
   }};  
   @Override  
   public String getSecretKey(String userName) {  
     return usersKeys.get(userName).getSecretKey();  
   }  
   @Override  
   public void saveUserCredentials(String userName,  
                   String secretKey,  
                   int validationCode,  
                   List<Integer> scratchCodes) {  
     usersKeys.put(userName, new UserTOTP(userName, secretKey, validationCode, scratchCodes));  
   }  
   public UserTOTP getUser(String username) {  
     return usersKeys.get(username);  
   }  
 }  

Notice that, we are right now hard-coding some users in our repository, in a real-world scenario you can use your DB to fetch users instead of hard-coded values.

    Now we just need a few endpoints to access these services. So let's create a couple of endpoints as below.

file name: AppController.java
 package com.techreloded;  
 import com.google.zxing.BarcodeFormat;  
 import com.google.zxing.client.j2se.MatrixToImageWriter;  
 import com.google.zxing.common.BitMatrix;  
 import com.google.zxing.qrcode.QRCodeWriter;  
 import com.warrenstrange.googleauth.GoogleAuthenticator;  
 import com.warrenstrange.googleauth.GoogleAuthenticatorKey;  
 import com.warrenstrange.googleauth.GoogleAuthenticatorQRGenerator;  
 import lombok.RequiredArgsConstructor;  
 import lombok.SneakyThrows;  
 import lombok.extern.slf4j.Slf4j;  
 import org.springframework.web.bind.annotation.*;  
 import javax.servlet.ServletOutputStream;  
 import javax.servlet.http.HttpServletResponse;  
 import java.util.List;  
 @Slf4j  
 @RestController  
 @RequiredArgsConstructor  
 @RequestMapping("/code")  
 public class AppController {  
   private final GoogleAuthenticator gAuth;  
   private final CredentialRepository credentialRepository;  
   @SneakyThrows  
   @GetMapping("/generate/{username}")  
   public void generate(@PathVariable String username, HttpServletResponse response) {  
     final GoogleAuthenticatorKey key = gAuth.createCredentials(username);  
     QRCodeWriter qrCodeWriter = new QRCodeWriter();  
     String otpAuthURL = GoogleAuthenticatorQRGenerator.getOtpAuthTotpURL("my-demo", username, key);  
     BitMatrix bitMatrix = qrCodeWriter.encode(otpAuthURL, BarcodeFormat.QR_CODE, 200, 200);  
     ServletOutputStream outputStream = response.getOutputStream();  
     MatrixToImageWriter.writeToStream(bitMatrix, "PNG", outputStream);  
     outputStream.close();  
   }  
   @PostMapping("/validate/key")  
   public Validation validateKey(@RequestBody ValidateCodeDto body) {  
     return new Validation(gAuth.authorizeUser(body.getUsername(), body.getCode()));  
   }  
   @GetMapping("/scratches/{username}")  
   public List<Integer> getScratches(@PathVariable String username) {  
     return getScratchCodes(username);  
   }  
   private List<Integer> getScratchCodes(@PathVariable String username) {  
     return credentialRepository.getUser(username).getScratchCodes();  
   }  
   @PostMapping("/scratches/")  
   public Validation validateScratch(@RequestBody ValidateCodeDto body) {  
     List<Integer> scratchCodes = getScratchCodes(body.getUsername());  
     Validation validation = new Validation(scratchCodes.contains(body.getCode()));  
     scratchCodes.remove(body.getCode());  
     return validation;  
   }  
 }  

Below are the endpoints of our services.



Hit the 1st endpoint to generate a QR code.

End Point: http://localhost:8080/code/generate/gaurav.mute@gmail.com



        Next, open the Google Authenticator app on your smartphone and scan this QR code, resulting in a new account will be added to the app. It will start generating Time base OTP in the app.




You can use the below endpoint by passing OTP visible into App to validate the user.




End Point: http://localhost:8080/code/validate/key

Body: 
 {  
  "code": 973498,  
  "username": "gaurav.mute@gmail.com"  
 }  

Below are success cases: 


After a few seconds, TOTP will expire and results in a false response as below.



        That is the very basic implementation of Google 2-FA authentication in Spring Boot. you can find the entire source code used for this POC on my GitHub. 



        Hope you found this blog informative, do post your comment down below if you have any, and see you in my next blog, till then take care of yourself and your loved ones.


Comments

Popular posts from this blog

Jasper report integration in Spring boot/Spring MVC.

FireBase Crud operation in Spring Boot

Hybris Overview and b2c installation initialization