vahiiiid/go-rest-api-boilerplate

Add Forgot Password / Password Reset Flow

Open

#10 opened on Oct 6, 2025

 (2 comments) (0 reactions) (1 assignee)Go (23 forks)auto 404
enhancementhacktoberfesthelp wanted

Repository metrics

Stars
 (60 stars)
PR merge metrics
 (Avg merge 11m) (1 merged PR in 30d)

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/rand for 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) error
    • GetResetToken(token string) (*PasswordResetToken, error)
    • MarkTokenAsUsed(tokenID uint) error
    • InvalidateUserTokens(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

🎓 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!

Contributor guide