Secure Authentication with Redis + Twilio : Rate Limiting OTP Generation in Spring Boot
“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:
- Speed and Efficiency: Redis’s in-memory nature makes it exceptionally fast in data retrieval and storage. .
- 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.
- 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.
- Atomic Operations: Redis supports atomic operations like
SETNX
(set if not exists) andINCR
(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:
- 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.
- 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.
- 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.