Creating Authentication Using Phone Number and OTP with Spring Boot, Spring Security

Abhishek Ranjan
5 min readApr 13, 2023

--

Introduction:

In this article, we will walk you through the process of creating authentication using phone numbers and One-Time Passwords (OTPs) in a Spring Boot application. We will be using Spring Security to secure our application and a placeholder third-party provider for sending OTPs. This tutorial assumes that you have a basic understanding of Spring Boot and Spring Security. We’ll also include code examples and Mermaid diagrams for better understanding.

Step 1: Setting up the Spring Boot project

  1. Go to the Spring Initializr website (https://start.spring.io/) to generate a new Spring Boot project with the required dependencies.
  2. Select the following dependencies: “Web”, “Security”, and “JPA”.
  3. Click “Generate” to download the generated project and unzip it.

Step 2: Configure Spring Security

  1. Add the following dependencies to your pom.xml file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

2. Create a new class called SecurityConfig in the com.example.demo.config package, and make it extend WebSecurityConfigurerAdapter:

package com.example.demo.config;

import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}

Step 3: Implementing phone number authentication

  1. Create a new package called com.example.demo.security.
  2. In the com.example.demo.security package, create a new class called PhoneNumberAuthenticationProvider and make it extend AuthenticationProvider:
package com.example.demo.security;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
public class PhoneNumberAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// Implement phone number authentication logic here
}
@Override
public boolean supports(Class<?> authentication) {
return PhoneNumberAuthenticationToken.class.isAssignableFrom(authentication);
}
}
  1. In the PhoneNumberAuthenticationProvider class, implement the authenticate method. This method should verify the user's phone number and return an Authentication object if the user is authenticated.
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String phoneNumber = authentication.getName();
// Verify the phone number and return an Authentication object if the user is authenticated.
// Use your placeholder third-party provider for phone number verification.
}

Step 4: Create a custom authentication filter

  1. In the com.example.demo.security package, create a new class called PhoneNumberAuthenticationFilter and make it extend AbstractAuthenticationProcessingFilter:
package com.example.demo.security;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
public class PhoneNumberAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public PhoneNumberAuthenticationFilter() {
super(new AntPathRequestMatcher("/login/phone", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
// Implement phone number authentication logic here
}
}

2. Implement the attemptAuthentication method in the PhoneNumberAuthenticationFilter class. This method should extract the user's phone number from the request, create an Authentication object, and pass it to the AuthenticationManager.

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws Authentication throws AuthenticationException, IOException, ServletException {
String phoneNumber = request.getParameter("phoneNumber");

PhoneNumberAuthenticationToken authRequest = new PhoneNumberAuthenticationToken(phoneNumber);
return getAuthenticationManager().authenticate(authRequest);
}

Step 5: Configuring the authentication filter and provider in the SecurityConfig class

  1. In the SecurityConfig class, add the following methods to register the PhoneNumberAuthenticationFilter and PhoneNumberAuthenticationProvider:
package com.example.demo.config;

import com.example.demo.security.PhoneNumberAuthenticationFilter;
import com.example.demo.security.PhoneNumberAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PhoneNumberAuthenticationProvider phoneNumberAuthenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(phoneNumberAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authenticationProvider(phoneNumberAuthenticationProvider)
.authorizeRequests()
.antMatchers("/login/phone").permitAll()
.anyRequest().authenticated();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(phoneNumberAuthenticationProvider);
}
@Bean
public PhoneNumberAuthenticationFilter phoneNumberAuthenticationFilter() throws Exception {
PhoneNumberAuthenticationFilter filter = new PhoneNumberAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}

Step 6: Implementing the OTP generation and validation

  1. In the com.example.demo.service package, create a new interface called OTPService with the following methods:
public interface OTPService {
String generateOTP(String phoneNumber);
boolean validateOTP(String phoneNumber, String otp);
}

2. Create a new class called OTPServiceImpl in the com.example.demo.service.impl package and implement the OTPService interface:

@Service
public class OTPServiceImpl implements OTPService {
// Implement your third-party OTP provider here
@Override
public String generateOTP(String phoneNumber) {
// Call the third-party OTP provider to generate an OTP and return it
}
@Override
public boolean validateOTP(String phoneNumber, String otp) {
// Call the third-party OTP provider to validate the OTP and return true if valid, false otherwise
}
}

Step 7: Implementing the OTP generation and validation in the PhoneNumberAuthenticationProvider class

  1. Inject the OTPService into the PhoneNumberAuthenticationProvider class:
@Autowired
private OTPService otpService;

2. Update the authenticate method in the PhoneNumberAuthenticationProvider class to generate an OTP and send it to the user via the third-party provider:

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String phoneNumber = authentication.getName();
String otp = otpService.generateOTP(phoneNumber);
// Send the generated OTP to the user's phone number using the third-party provider
// Store the generated OTP in the user's session or another storage mechanism for later validation
return null; // Return null since the user is not yet authenticated
}

3. Add a new method called validateOTP in the PhoneNumberAuthenticationProvider class to validate the OTP entered by the user:

public Authentication validateOTP(String phoneNumber, String otp) throws AuthenticationException {
// Validate the OTP entered by the user using the OTPService
boolean isValid = otpService.validateOTP(phoneNumber, otp);
if (isValid) {
// If the OTP is valid, create an authenticated Authentication object with the user's phone number
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
PhoneNumberAuthenticationToken authentication = new PhoneNumberAuthenticationToken(phoneNumber, authorities);
return authentication;
} else {
// If the OTP is not valid, throw an AuthenticationException
throw new BadCredentialsException("Invalid OTP");
}
}

Step 8: Implement the OTP validation in the PhoneNumberAuthenticationFilter class

  1. Update the attemptAuthentication method in the PhoneNumberAuthenticationFilter class to validate the OTP entered by the user:
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
String phoneNumber = request.getParameter("phoneNumber");
String otp = request.getParameter("otp");

PhoneNumberAuthenticationToken authRequest = new PhoneNumberAuthenticationToken(phoneNumber);
Authentication authentication = getAuthenticationManager().authenticate(authRequest);
if (authentication == null) {
// If the user is not yet authenticated, call the validateOTP method in the PhoneNumberAuthenticationProvider class
PhoneNumberAuthenticationProvider provider = (PhoneNumberAuthenticationProvider) getAuthenticationManager()
.getProviders().stream().filter(p -> p instanceof PhoneNumberAuthenticationProvider).findFirst()
.orElseThrow(() -> new AuthenticationServiceException("PhoneNumberAuthenticationProvider not found"));
authentication = provider.validateOTP(phoneNumber, otp);
}
return authentication;
}

Step 9: Testing the authentication flow

  1. Create a simple controller for testing purposes:
@RestController
public class TestController {
@GetMapping("/")
public ResponseEntity<String> home() {
return ResponseEntity.ok("Welcome to the home page!");
}
@GetMapping("/login/phone")
public ResponseEntity<String> login() {
return ResponseEntity.ok("Please provide your phone number and OTP to authenticate.");
}
}

2. Run the application and test the authentication flow using a tool like Postman or Curl. Send a POST request to /login/phone with the phone number and OTP as parameters. If the authentication is successful, you should receive a response with the authenticated user's phone number and authorities.

Conclusion:

In this article, we have walked you through the process of creating authentication using phone numbers and OTPs in a Spring Boot application using Spring Security and a placeholder third-party provider for sending OTPs. By following these steps, you can implement a secure and reliable authentication mechanism for your application.

Feel free to modify and extend the code as needed to fit your specific requirements. Happy coding! .

For more exclusive articles visit https://hacktivate.substack.com/ and subscribe to the newsletter.

--

--