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
 (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/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