Ever felt like passwords are your kryptonite?
We’ve all been there. Forgetting passwords, trying to create the perfect combo of letters, numbers, and symbols. Then there’s the “forgot password” reset loop that makes you want to throw your phone into the ocean. 😤
Well, fret no more, because OTP authentication is here to save the day! Forget passwords and embrace the magic of One-Time Passwords (OTPs)—the true passwordless superhero your app needs.
What is OTP Authentication?
OTP (One-Time Password) authentication is exactly what it sounds like—a magical password that’s valid for only one single login attempt. When users try to log in, instead of entering a password, they’ll receive an OTP via email (or SMS). This OTP is like a secret key that grants access for a brief time—secure, easy, and oh-so-convenient.
But wait, OTP is only the beginning of the superhero saga! We pair it with Access Tokens and Refresh Tokens, creating an even more powerful and secure way to keep users safe while making login easy-breezy. 🌬️
How Does OTP Authentication Work?
Let’s break it down with a simple example:
Step 1: The Magic Request 🪄
- You (the user) open up your favorite app, let’s say it’s SuperApp. Instead of asking for your password, the app simply asks for your email address. You enter it in.
Step 2: The OTP Arrives 📨
- The app sends a magical OTP (something like 123456) to your email. This code is valid for a limited time—usually a few minutes.
Step 3: The OTP Entered 🧑💻
- You open your inbox, grab that OTP, and enter it into the app.
Step 4: Access Granted! 🎉
- The app verifies the OTP and—BOOM! You’re in, no password needed! In the backend, the app generates an Access Token, which is like a VIP badge for you to use the app.
Step 5: The Power of Refresh Tokens 🔄
- What if your Access Token expires (after 15 minutes, for example)? Don’t worry! The Refresh Token will keep you logged in and grant you a fresh Access Token without needing to ask for a new OTP.
"Spring Boot: Like a magical kitchen, where the ingredients are already prepared, you just need to assemble them—no need to break a sweat! 🍳"
Code: The Nuts and Bolts of OTP + JWT Authentication
Let’s dive into some of the key snippets from the GitHub repository to make things super simple
1. Generate OTP and Send via Email
We generate an OTP and send it to the user's email. Here's the magic:
public static String generateOTP(int length) {
SecureRandom random = new SecureRandom();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append(random.nextInt(10)); // Generates a random digit (0-9)
}
return sb.toString();
}
public void sendEmail(EmailDto emailDto) {
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setTo(emailDto.getTo());
simpleMailMessage.setSubject(emailDto.getSubject());
simpleMailMessage.setText(emailDto.getBody());
javaMailSender.send(simpleMailMessage);
}
2. OTP Verification and JWT Token Generation
Once the user enters the OTP, we verify it and generate JWT tokens.
public TokenResponseDto generateToken(String code) {
Optional<LoginCode> loginCode = loginCodeRepository.findByCode(code);
if (loginCode.isEmpty() || loginCode.get().getExpirationTime().isBefore(LocalDateTime.now())) {
throw new InvalidDataException("Invalid or expired code");
}
User user = userRepository.findByEmail(loginCode.get().getEmail())
.orElseThrow(() -> new RuntimeException("User not found"));
// Generate JWT
String accessToken = Jwts.builder()
.setClaims(Map.of("roles", user.getRoles(), "sub", loginCode.get().getEmail()))
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION_TIME))
.signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
.compact();
String refreshToken = Jwts.builder()
.setSubject(loginCode.get().getEmail())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + REFRESH_TOKEN_EXPIRATION_TIME))
.signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
.compact();
// Remove the used login token
loginCodeRepository.delete(loginCode.get());
return TokenResponseDto.builder()
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}
3. Token Refreshing
If the access token expires, we use the refresh token to issue a new access token without requiring the user to re-enter the OTP:
public TokenResponseDto refreshToken(String refreshToken) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
.build()
.parseClaimsJws(refreshToken)
.getBody();
User user = userRepository.findByEmail(claims.getSubject())
.orElseThrow(() -> new RuntimeException("User not found!"));
String newToken = Jwts.builder()
.setSubject(claims.getSubject())
.setClaims(Map.of("roles", user.getRoles(), "sub", claims.getSubject()))
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION_TIME))
.signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
.compact();
return TokenResponseDto.builder()
.accessToken(newToken)
.refreshToken(refreshToken)
.build();
} catch (Exception e) {
throw new UnauthorizedAccessException("Unauthorized access!");
}
}
4. Role-Based Access Control
Now, let's add some roles to make our authentication even more interesting. A system with roles ensures that only specific users can access particular resources. For example, Admin users may have access to modify user data, while Regular users can just view their data.
Here’s how we implement roles:
Step 1: Assign Roles to JWT Tokens
When generating the access token, we can include the user's roles within the token as claims. Let's say we have two roles: ADMIN and USER. The ADMIN role has higher privileges, and the USER role has limited access.
String accessToken = Jwts.builder()
.setClaims(Map.of("roles", user.getRoles(), "sub", loginCode.get().getEmail()))
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION_TIME))
.signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
.compact();
Step 2: Extract Roles from JWT Tokens
When a user makes a request, we can extract their roles from the JWT token to determine if they have access to a specific endpoint.
Claims claims = Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
.build()
.parseClaimsJws(token)
.getBody();
String email = claims.getSubject();
List<String> roles = (List<String>) claims.get("roles");
Step 3: Use Roles to Control Access to APIs (RBAC)
Now that we have the roles, we can apply role-based access control (RBAC) to protect certain API endpoints. For example, only ADMINs can access admin APIs, while USERs can access user-specific data.
In Spring Security, we can configure role-based access like this:
@GetMapping("/{id}")
@PreAuthorize("hasAuthority('USER')")
public ResponseEntity<UserDto> getUser(@PathVariable String id) {
UserDto user = userService.getUser(UUID.fromString(id));
return ResponseEntity.ok(user);
}
@GetMapping
@PreAuthorize("hasAuthority('ADMIN')")
public ResponseEntity<List<UserDto>> getUsers() {
return ResponseEntity.ok(userService.getUsers());
}
The Pros of OTP Authentication (The Good Stuff!)
-
No More Passwords! 🙌
- Forget trying to remember a password! OTPs are simple, secure, and easy to use.
-
Stronger Security 💪
- One-time-use codes make hacking or phishing attempts virtually useless.
-
Quick & Easy 🚀
- Logging in is a breeze—just check your email, enter the code, and you’re in.
-
Refresh Tokens = Long-Term Access🔄
- Refresh Tokens make your login experience even smoother by keeping you logged in without the need to re-enter codes.
The Cons (Every Superhero Has a Weakness)
-
Relies on Email Access 📧
- If you can’t access your email, you can’t log in! Always keep your inbox handy!
-
Token Interception Risk ⚠️
- If someone intercepts your Refresh Token, they could potentially gain access. Always use HTTPS and ensure proper security measures!
Final Thoughts: OTP Authentication = The Passwordless Future!
So there you have it! OTP Authentication is like a passwordless superhero that swoops in to save your app and its users. By pairing OTPs with Access Tokens and Refresh Tokens, you create a smooth, secure, and seamless login experience.
Say goodbye to remembering complex passwords and hello to the future of easy, magic-like authentication. 🪄✨
If you haven’t already, it’s time to embrace OTP and leave the password struggles behind. Your users (and your app) will thank you! 🙏
GitHub Repository
You can find the full code and setup for this OTP + JWT authentication in my GitHub repo: OTP Authentication with JWT Example
Let’s Discuss!
Do you have suggestions, feedback, or new ideas for improving OTP authentication? Let me know in the comments below!
Feel free to share this blog with your network and let others join the passwordless revolution. 🚀
Looking forward to your thoughts! 🙌
Top comments (0)