Secure Authentication with Redis + Twilio : Rate Limiting OTP Generation in Spring Boot

The Java Trail
4 min readSep 7, 2023

--

“OTP” stands for “One-Time Password.” It is a security measure used to authenticate users and verify their identity during online transactions or account logins. A one-time password is a unique and temporary code that is typically valid for a short period of time, usually for a single login session or transaction. Since Redis has time-based expiration using the EXPIRE command, it is well-suited for building an OTP (One-Time Password) system. Redis can be used as an OTP (One-Time Password) generator in conjunction with other components to provide secure authentication for various applications.

OTP Generation with Redis:

  1. Speed and Efficiency: Redis’s in-memory nature makes it exceptionally fast in data retrieval and storage. .
  2. Key-Value Storage: Redis operates as a key-value store, where each OTP is associated with a unique key (e.g., user identifier or session ID). Storing OTPs in Redis ensures that they can be easily accessed, verified, and invalidated when needed.
  3. Time-Based Expiration: Redis allows setting a time-to-live (TTL) for keys, making it perfect for OTPs. OTPs can be stored with a TTL, ensuring they are automatically removed from Redis after a specified period, enhancing security by limiting their validity.
  4. Atomic Operations: Redis supports atomic operations like SETNX (set if not exists) and INCR (increment) which are invaluable for OTP generation and rate limiting.

Rate Limiting with Redis:

Rate limiting is a crucial mechanism for preventing abuse, brute force attacks, and ensuring fair resource utilization in applications. Redis can be effectively used for rate limiting:

  1. Key-Based Counting: Redis enables developers to maintain counters associated with specific keys (e.g., user IP addresses or session IDs). These counters can track the number of requests or actions within a given time frame.
  2. Expiration and Reset: Redis allows setting a TTL for these counters, acting as a sliding window. When the TTL expires, the counter is reset, and the user can perform actions again. This approach is more efficient than manual tracking.
  3. Efficient Rate Limiting Algorithms: Redis can be used to implement various rate limiting algorithms like token bucket or leaky bucket. These algorithms efficiently control the rate at which actions can be performed, providing a scalable solution.

Combining OTP Generation and Rate Limiting with Redis:

The synergy between OTP generation and rate limiting using Redis is particularly powerful. For instance, in user authentication, Redis can:

  • Generate OTPs on request, storing them with a TTL.
  • Use rate limiting to restrict the number of OTP generation attempts within a specified time frame, preventing abuse and protecting user accounts.
  • Store both the OTPs and rate-limiting information together, simplifying management and ensuring security.

SpringBoot Implementation

Step 1: Set Up Your Spring Boot Project

Create a new Spring Boot project with the necessary dependencies for Spring Web, Spring Data Redis, and Twilio. Use Spring Initializer or your IDE to set up the project.

    <dependencies>
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- Twilio -->
<dependency>
<groupId>com.twilio.sdk</groupId>
<artifactId>twilio</artifactId>
<version>8.28.0</version> <!-- Use the latest Twilio version -->
</dependency>
</dependencies>

Step 2: Configure Redis

Configure your Redis connection in the application.properties or application.yml:

spring.redis.host=localhost
spring.redis.port=6379

Step 3: Add Twilio Configuration

Add your Twilio configuration in the application.properties:

twilio.account-sid=your_account_sid
twilio.auth-token=your_auth_token
twilio.phone-number=your_twilio_phone_number

Step 4: Create Configuration Classes

Create a configuration class to load the Twilio configuration properties:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TwilioConfig {

@Value("${twilio.account-sid}")
private String accountSid;

@Value("${twilio.auth-token}")
private String authToken;

@Value("${twilio.phone-number}")
private String phoneNumber;

public String getAccountSid() {
return accountSid;
}

public String getAuthToken() {
return authToken;
}

public String getPhoneNumber() {
return phoneNumber;
}
}

Step 5: Implement OTP Generation and Verification

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Random;

@Service
public class OTPService {

@Autowired
private StringRedisTemplate redisTemplate;

@Autowired
private TwilioService twilioService;

private static final String OTP_PREFIX = "otp:";
private static final String RATE_LIMIT_PREFIX = "rate_limit:";
private static final int RATE_LIMIT = 5;
private static final long RATE_LIMIT_PERIOD_SECONDS = 60;

public String generateOTP(String phoneNumber) {
// Check rate limiting
String rateLimitKey = RATE_LIMIT_PREFIX + phoneNumber;
if (!isRateLimited(rateLimitKey)) {
return "Rate limit exceeded. Please try again later.";
}

// Generate OTP
String otpKey = OTP_PREFIX + phoneNumber;
String existingOTP = redisTemplate.opsForValue().get(otpKey);
if (existingOTP != null) {
return "An OTP has already been generated for this number. Please check your SMS.";
}

String otpCode = generateRandomOTP(6);
redisTemplate.opsForValue().set(otpKey, otpCode, Duration.ofMinutes(5));

twilioService.sendOTP(phoneNumber, otpCode);

return "OTP generated and sent to " + phoneNumber;
}

public boolean verifyOTP(String phoneNumber, String otpCode) {
String otpKey = OTP_PREFIX + phoneNumber;
String storedOTP = redisTemplate.opsForValue().get(otpKey);

if (storedOTP != null && storedOTP.equals(otpCode)) {
redisTemplate.delete(otpKey);
return true;
}

return false;
}

private boolean isRateLimited(String rateLimitKey) {
Long currentCount = redisTemplate.opsForValue().increment(rateLimitKey, 1);
if (currentCount == 1) {
redisTemplate.expire(rateLimitKey, RATE_LIMIT_PERIOD_SECONDS, TimeUnit.SECONDS);
}
return currentCount <= RATE_LIMIT;
}

// Other methods as needed
}

Step 6: Implement TwilioService for OTP Delivery

Create a TwilioService to send OTPs via Twilio:

import com.twilio.Twilio;
import com.twilio.rest.api.v2010.account.Message;
import com.twilio.type.PhoneNumber;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class TwilioService {

private final TwilioConfig twilioConfig;

@Autowired
public TwilioService(TwilioConfig twilioConfig) {
this.twilioConfig = twilioConfig;
Twilio.init(twilioConfig.getAccountSid(), twilioConfig.getAuthToken());
}

public void sendOTP(String phoneNumber, String otpCode) {
Message message = Message.creator(
new PhoneNumber(phoneNumber),
new PhoneNumber(twilioConfig.getPhoneNumber()),
"Your OTP is: " + otpCode)
.create();
}
}

Step 7: Create REST Endpoints

Create REST endpoints in a controller for OTP generation and verification:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/otp")
public class OTPController {

@Autowired
private OTPService otpService;

@PostMapping("/generate")
public ResponseEntity<String> generateOTP(@RequestParam String phoneNumber) {
String result = otpService.generateOTP(phoneNumber);
return ResponseEntity.ok(result);
}

@PostMapping("/verify")
public ResponseEntity<String> verifyOTP(@RequestParam String phoneNumber, @RequestParam String otpCode) {
boolean isValid = otpService.verifyOTP(phoneNumber, otpCode);
if (isValid) {
return ResponseEntity.ok("OTP verification successful");
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid OTP");
}
}

// Other endpoints as needed
}

Step 8: Run Your Spring Boot Application

Run your Spring Boot application. You can now use the /otp/generate and /otp/verify endpoints to generate and verify OTPs securely with rate limiting.

--

--

The Java Trail

Scalable Distributed System, Backend Performance Optimization, Java Enthusiast. (mazumder.dip.auvi@gmail.com Or, +8801741240520)