Add Forgot Password / Password Reset Flow
#10 opened on Oct 6, 2025
Repository metrics
- Stars
- (60 stars)
- PR merge metrics
- (PR metrics pending)
Description
🎯 Goal
Implement a complete forgot password and password reset flow, allowing users to securely reset their passwords via email verification tokens.
📋 Description
Add a production-ready password reset feature that follows security best practices. Users should be able to request a password reset, receive a secure token, and use that token to set a new password.
✅ Acceptance Criteria
1. Database Schema
- Create migration:
migrations/000002_add_password_reset_tokens.up.sql - Create table structure:
CREATE TABLE password_reset_tokens (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token VARCHAR(255) NOT NULL UNIQUE,
expires_at TIMESTAMP NOT NULL,
used BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW(),
INDEX idx_token (token),
INDEX idx_user_id (user_id)
);
- Create corresponding down migration
2. Models
- Create
internal/auth/reset_token_model.go:
type PasswordResetToken struct {
ID uint `gorm:"primaryKey"`
UserID uint `gorm:"not null"`
Token string `gorm:"uniqueIndex;not null"`
ExpiresAt time.Time `gorm:"not null"`
Used bool `gorm:"default:false"`
CreatedAt time.Time
User User `gorm:"foreignKey:UserID"`
}
3. New Endpoints
Request Password Reset
- POST
/api/v1/auth/forgot-password - Request body:
{
"email": "user@example.com"
}
- Response (200 OK - always success to prevent email enumeration):
{
"message": "If the email exists, a password reset link has been sent."
}
Reset Password
- POST
/api/v1/auth/reset-password - Request body:
{
"token": "abc123...",
"new_password": "newSecurePass123"
}
- Response (200 OK):
{
"message": "Password has been reset successfully."
}
4. Business Logic (Service Layer)
Token Generation
- Generate cryptographically secure random token (32+ bytes)
- Use
crypto/randfor token generation - Hash token before storing in database (use SHA-256)
- Set expiration time (default: 1 hour)
- Invalidate previous unused tokens for the user
Token Validation
- Verify token exists and matches hash
- Check token hasn't expired
- Check token hasn't been used
- Validate new password strength (min 6 chars, as per current validation)
Password Update
- Hash new password with bcrypt
- Update user password in database
- Mark token as used
- Optional: Invalidate all user sessions/JWTs
5. Email Notification
- Create email service interface:
internal/email/service.go - For MVP: Log reset link to console (with clear documentation)
- Structure for future email integration (SMTP/SendGrid/etc.)
- Reset link format:
http://frontend-url/reset-password?token={token}
Example console output:
[PASSWORD RESET] Email would be sent to: user@example.com
[PASSWORD RESET] Reset link: http://localhost:3000/reset-password?token=abc123xyz
[PASSWORD RESET] Token expires at: 2025-10-06 11:30:00
6. Security Best Practices
- Always return same response (prevent email enumeration)
- Rate limit forgot-password endpoint (5 requests/hour per IP)
- Token expires after 1 hour
- Token is single-use only
- Store hashed tokens in database
- Invalidate old tokens when new one requested
- Use constant-time comparison for token validation
7. Repository Layer
- Add to
internal/auth/repository.go:CreateResetToken(userID uint, token string, expiresAt time.Time) errorGetResetToken(token string) (*PasswordResetToken, error)MarkTokenAsUsed(tokenID uint) errorInvalidateUserTokens(userID uint) error
8. Handler Layer
- Add to
internal/auth/handler.go:ForgotPasswordHandler(c *gin.Context)ResetPasswordHandler(c *gin.Context)
- Add proper Swagger annotations
- Validate request bodies
- Handle errors appropriately
9. Testing
- Test token generation and hashing
- Test token expiration
- Test token reuse prevention
- Test invalid token handling
- Test successful password reset flow
- Test email enumeration prevention
- Add integration tests
10. Documentation
- Update Swagger documentation with new endpoints
- Update README.md with password reset flow
- Add example curl commands
- Document email service interface for future implementation
- Update Postman collection
💡 Implementation Hints
Token Generation Example
import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
)
func GenerateResetToken() (plainToken string, hashedToken string, err error) {
bytes := make([]byte, 32)
if _, err := rand.Read(bytes); err != nil {
return "", "", err
}
plainToken = hex.EncodeToString(bytes)
hash := sha256.Sum256([]byte(plainToken))
hashedToken = hex.EncodeToString(hash[:])
return plainToken, hashedToken, nil
}
Register Routes
In internal/server/router.go:
auth := v1.Group("/auth")
{
auth.POST("/forgot-password", authHandler.ForgotPasswordHandler)
auth.POST("/reset-password", authHandler.ResetPasswordHandler)
}
Email Service Interface
type EmailService interface {
SendPasswordResetEmail(to, token string) error
}
// Console implementation for development
type ConsoleEmailService struct{}
func (s *ConsoleEmailService) SendPasswordResetEmail(to, token string) error {
log.Printf("[PASSWORD RESET] Email to: %s", to)
log.Printf("[PASSWORD RESET] Token: %s", token)
return nil
}
📚 Resources
- OWASP Password Reset Guidelines
- Go crypto/rand Documentation
- Token-Based Authentication Best Practices
🎓 Difficulty Level
Intermediate to Advanced - Requires understanding of security, database migrations, multi-layer architecture, and token management.
Note: Security is critical for this feature. Follow OWASP guidelines and ensure all acceptance criteria are met. Run make lint, make test, and make migrate-up before submitting your PR!