Creating Authentication Using Phone Number and OTP with Spring Boot, Spring Security
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
- Go to the Spring Initializr website (https://start.spring.io/) to generate a new Spring Boot project with the required dependencies.
- Select the following dependencies: “Web”, “Security”, and “JPA”.
- Click “Generate” to download the generated project and unzip it.
Step 2: Configure Spring Security
- 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
- Create a new package called
com.example.demo.security
. - In the
com.example.demo.security
package, create a new class calledPhoneNumberAuthenticationProvider
and make it extendAuthenticationProvider
:
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);
}
}
- In the
PhoneNumberAuthenticationProvider
class, implement theauthenticate
method. This method should verify the user's phone number and return anAuthentication
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
- In the
com.example.demo.security
package, create a new class calledPhoneNumberAuthenticationFilter
and make it extendAbstractAuthenticationProcessingFilter
:
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
- In the
SecurityConfig
class, add the following methods to register thePhoneNumberAuthenticationFilter
andPhoneNumberAuthenticationProvider
:
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
- In the
com.example.demo.service
package, create a new interface calledOTPService
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
- Inject the
OTPService
into thePhoneNumberAuthenticationProvider
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
- Update the
attemptAuthentication
method in thePhoneNumberAuthenticationFilter
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
- 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.