When it comes to building modern backend applications, one of the most critical features is authentication. In this post, I’ll walk you through how I built a complete authentication flow using Dart Frog, a fast, minimalistic backend framework powered by Dart.
This is part of the backend powering [SpeakUp], a multi-author content platform currently in development.
🧱 Features Covered
Here’s what we implemented:
- User Registration & Login
- JWT Authentication with Middleware
- Password Reset Flow
- OTP generation and validation
- Email notification
- Temporary token for added security
- Final password update
- Clean Architecture using Services, Repositories, and Middleware
- PostgreSQL as our database
- Cron Jobs for background tasks (like cleaning up expired OTPs)
🔐 JWT Authentication
We use JWT (JSON Web Token) to handle secure sessions. Upon successful login, a token is generated and returned:
static String generateToken(Map<String, dynamic> claims) {
final jwt = JWT(claims, issuer: _jwtIssuer);
return jwt.sign(SecretKey(_secretKey), expiresIn: Duration(minutes: 120));
}
A middleware verifyHeaderTokenAndReturnUser is used to decode the token and attach the user to the context:
Middleware verifyHeaderTokenAndReturnUser() {
return (handler) => (context) async {
final authHeader = context.request.headers['Authorization'];
final token = authHeader?.replaceFirst('Bearer ', '');
final decoded = JWTUtilis.verifyToken(token);
return handler(context.provide(() => User(...)));
};
}
🔁 Password Reset Flow with OTP
Step 1: Request OTP
We generate a 6-digit OTP, store it in the database, and send it to the user’s email:
CREATE TABLE speakup_password_reset_table (
email VARCHAR NOT NULL,
otp VARCHAR(6) NOT NULL,
expires_at TIMESTAMP NOT NULL,
is_used BOOLEAN DEFAULT FALSE
);
Rate limiting was handled by checking recent OTPs sent in the last minute:
if (lastOtpCreatedAt.difference(now).inSeconds < 60) {
return ApiResponse.tooManyRequests('Try again later.');
}
Step 2: OTP Verification
We validate that the OTP is correct and not expired. If valid, we generate a temporary token that can be used only to reset the password.
final tempToken = JWTUtilis.generateToken({
'email': email,
'purpose': 'reset_password',
'exp': DateTime.now().add(Duration(minutes: 10)).millisecondsSinceEpoch
});
Step 3: Set New Password
With the temporary token, users can now securely reset their password. We verify the token’s claims and hash the new password using bcrypt before updating the DB.
🛠 Cron Job for Cleanup
We used the cron package to run a cleanup job every 10 minutes to delete expired or used OTPs:
cron.schedule(Schedule.parse('*/10 * * * *'), () async {
await connection.execute('''
DELETE FROM speakup_password_reset_table
WHERE expires_at < NOW() OR is_used = TRUE;
''');
});
💡 Key Takeaways
- Dart Frog provides a flexible and clean way to build robust backend APIs.
- PostgreSQL is powerful enough for relational needs including rate limiting and OTP tracking.
- Middleware in Dart Frog makes token verification seamless.
- Cron jobs help manage background cleanup tasks in a Dart-native way.
🔚 Final Thoughts
Dart Frog has proved itself as a reliable option for building a full-featured bac
If you’re a Dart developer or a startup looking for a modern, cost-effective backend stack—Dart Frog is worth your attention.