Rate Limiting and Security

Learning Objectives: By the end of this lesson, you will be able to:

  • Implement sophisticated rate limiting algorithms using Redis
  • Secure Redis deployments with authentication, encryption, and network controls
  • Build DDoS protection and abuse prevention systems
  • Design API throttling mechanisms for different user tiers
  • Implement security monitoring and intrusion detection with Redis

1. Welcome and Introduction (5 minutes)

Icebreaker Activity

Have you ever wondered how APIs like Twitter handle millions of requests per second without being overwhelmed? Or how financial systems prevent fraudulent transactions in real-time? The answer lies in intelligent rate limiting and security systems, and Redis provides the perfect foundation for building these critical protections!

Lesson Overview

  • Duration: 90 minutes
  • Prerequisites: Lessons 1-6 completed, understanding of HTTP APIs and security concepts
  • Materials Needed: Go environment, Redis server, basic cryptography knowledge

2. Learning Objectives and Session Outline (5 minutes)

What We’ll Cover Today:

  1. Rate Limiting Algorithms - Token bucket, sliding window, and distributed rate limiting
  2. API Security Patterns - Authentication, authorization, and abuse prevention
  3. Redis Security Hardening - Configuration, network security, and access controls
  4. DDoS Protection - Large-scale attack mitigation using Redis
  5. Fraud Detection - Real-time pattern analysis and anomaly detection
  6. Security Monitoring - Logging, alerting, and incident response

Success Criteria:

By the end of this session, you should be able to build production-ready security and rate limiting systems that protect applications from abuse while maintaining performance.

3. Foundation Knowledge (15-20 minutes)

Core Concepts

Rate Limiting Algorithms

1. Token Bucket Algorithm

  • Fixed capacity bucket filled with tokens at a constant rate
  • Each request consumes one token
  • When bucket is empty, requests are rejected or delayed

2. Sliding Window Algorithm

  • Track requests within a time window that “slides” with time
  • More accurate than fixed windows
  • Prevents burst traffic at window boundaries

3. Fixed Window Algorithm

  • Simple counter reset at fixed intervals
  • Can allow bursts at window boundaries
  • Easier to implement and understand

Security Threat Landscape

Common Attack Vectors

  • Brute Force: Repeated login attempts
  • API Abuse: Excessive requests to exhaust resources
  • DDoS: Distributed denial of service attacks
  • Data Scraping: Unauthorized data harvesting
  • Credential Stuffing: Using leaked credentials across services

Key Terminology

  • Rate Limiting: Controlling the frequency of requests from clients
  • Throttling: Slowing down requests rather than blocking them
  • Circuit Breaker: Automatically failing requests during system overload
  • IP Reputation: Scoring IP addresses based on historical behavior
  • Geofencing: Restricting access based on geographic location

4. Detailed Explanation and Examples (25-30 minutes)

Rate Limiting Architecture Patterns

1. Application-Level Rate Limiting

[Client] → [Load Balancer] → [Rate Limiter] → [Application] → [Database]
                                   ↑
                            [Redis Counter Store]

2. Distributed Rate Limiting

[Client] → [API Gateway] → [Multiple App Instances] → [Shared Redis Store]
              ↓                      ↓
        [Rate Limiter]         [Rate Limiter]
              ↓                      ↓
        [Shared State in Redis Cluster]

3. Multi-Layer Security

[Internet] → [CDN/WAF] → [Rate Limiter] → [Auth Layer] → [App] → [Redis]
     ↓            ↓            ↓             ↓
[DDoS Protect] [Geo Block] [API Limit] [User Auth] [Data Security]

5. Live Coding Demonstration (20-25 minutes)

Example 1: Advanced Rate Limiting System

package main

import (
    "context"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "log"
    "net/http"
    "strconv"
    "strings"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/redis/go-redis/v9"
)

// RateLimitAlgorithm defines different rate limiting strategies
type RateLimitAlgorithm int

const (
    TokenBucket RateLimitAlgorithm = iota
    SlidingWindow
    FixedWindow
)

// RateLimitConfig holds rate limiting configuration
type RateLimitConfig struct {
    Algorithm    RateLimitAlgorithm
    Limit        int           // requests per window
    Window       time.Duration // time window
    Burst        int           // burst capacity for token bucket
    KeyGenerator func(*gin.Context) string
}

// RateLimiter implements sophisticated rate limiting
type RateLimiter struct {
    client *redis.Client
    ctx    context.Context
    config RateLimitConfig
}

// RateLimitResult contains the result of rate limit check
type RateLimitResult struct {
    Allowed   bool
    Remaining int
    ResetTime time.Time
    RetryAfter time.Duration
}

func NewRateLimiter(client *redis.Client, config RateLimitConfig) *RateLimiter {
    return &RateLimiter{
        client: client,
        ctx:    context.Background(),
        config: config,
    }
}

// CheckLimit evaluates if request should be allowed
func (rl *RateLimiter) CheckLimit(c *gin.Context) (*RateLimitResult, error) {
    key := rl.config.KeyGenerator(c)
    
    switch rl.config.Algorithm {
    case TokenBucket:
        return rl.checkTokenBucket(key)
    case SlidingWindow:
        return rl.checkSlidingWindow(key)
    case FixedWindow:
        return rl.checkFixedWindow(key)
    default:
        return nil, fmt.Errorf("unknown algorithm: %v", rl.config.Algorithm)
    }
}

// Token Bucket Implementation
func (rl *RateLimiter) checkTokenBucket(key string) (*RateLimitResult, error) {
    bucketKey := fmt.Sprintf("bucket:%s", key)
    lastRefillKey := fmt.Sprintf("refill:%s", key)
    
    now := time.Now().Unix()
    
    // Lua script for atomic token bucket operation
    script := `
        local bucket_key = KEYS[1]
        local refill_key = KEYS[2]
        local limit = tonumber(ARGV[1])
        local burst = tonumber(ARGV[2])
        local window = tonumber(ARGV[3])
        local now = tonumber(ARGV[4])
        
        -- Get current tokens and last refill time
        local tokens = tonumber(redis.call('GET', bucket_key) or burst)
        local last_refill = tonumber(redis.call('GET', refill_key) or now)
        
        -- Calculate tokens to add based on time elapsed
        local elapsed = now - last_refill
        local tokens_to_add = math.floor(elapsed * limit / window)
        tokens = math.min(burst, tokens + tokens_to_add)
        
        -- Check if request can be served
        if tokens > 0 then
            tokens = tokens - 1
            redis.call('SET', bucket_key, tokens, 'EX', window * 2)
            redis.call('SET', refill_key, now, 'EX', window * 2)
            return {1, tokens, now + window}
        else
            -- Calculate retry after time
            local retry_after = math.ceil((1 - tokens) * window / limit)
            return {0, 0, now + retry_after}
        end
    `
    
    result, err := rl.client.Eval(rl.ctx, script, 
        []string{bucketKey, lastRefillKey},
        rl.config.Limit, rl.config.Burst, int(rl.config.Window.Seconds()), now).Result()
    
    if err != nil {
        return nil, fmt.Errorf("token bucket script failed: %w", err)
    }
    
    res := result.([]interface{})
    allowed := res[0].(int64) == 1
    remaining := int(res[1].(int64))
    resetTime := time.Unix(res[2].(int64), 0)
    
    return &RateLimitResult{
        Allowed:    allowed,
        Remaining:  remaining,
        ResetTime:  resetTime,
        RetryAfter: time.Until(resetTime),
    }, nil
}

// Sliding Window Implementation
func (rl *RateLimiter) checkSlidingWindow(key string) (*RateLimitResult, error) {
    windowKey := fmt.Sprintf("window:%s", key)
    now := time.Now().UnixMilli()
    windowStart := now - rl.config.Window.Milliseconds()
    
    // Lua script for sliding window
    script := `
        local key = KEYS[1]
        local now = tonumber(ARGV[1])
        local window_start = tonumber(ARGV[2])
        local limit = tonumber(ARGV[3])
        local window_ms = tonumber(ARGV[4])
        
        -- Remove old entries
        redis.call('ZREMRANGEBYSCORE', key, 0, window_start)
        
        -- Count current requests
        local current = redis.call('ZCARD', key)
        
        if current < limit then
            -- Add current request
            redis.call('ZADD', key, now, now)
            redis.call('EXPIRE', key, math.ceil(window_ms / 1000))
            return {1, limit - current - 1, now + window_ms}
        else
            return {0, 0, now + window_ms}
        end
    `
    
    result, err := rl.client.Eval(rl.ctx, script,
        []string{windowKey},
        now, windowStart, rl.config.Limit, rl.config.Window.Milliseconds()).Result()
    
    if err != nil {
        return nil, fmt.Errorf("sliding window script failed: %w", err)
    }
    
    res := result.([]interface{})
    allowed := res[0].(int64) == 1
    remaining := int(res[1].(int64))
    resetTime := time.UnixMilli(res[2].(int64))
    
    return &RateLimitResult{
        Allowed:    allowed,
        Remaining:  remaining,
        ResetTime:  resetTime,
        RetryAfter: time.Until(resetTime),
    }, nil
}

// Fixed Window Implementation
func (rl *RateLimiter) checkFixedWindow(key string) (*RateLimitResult, error) {
    windowKey := fmt.Sprintf("fixed:%s", key)
    now := time.Now()
    window := now.Truncate(rl.config.Window)
    windowEnd := window.Add(rl.config.Window)
    
    // Atomic increment with expiration
    pipe := rl.client.Pipeline()
    incrCmd := pipe.Incr(rl.ctx, windowKey)
    pipe.ExpireAt(rl.ctx, windowKey, windowEnd)
    
    _, err := pipe.Exec(rl.ctx)
    if err != nil {
        return nil, fmt.Errorf("fixed window operation failed: %w", err)
    }
    
    count := int(incrCmd.Val())
    allowed := count <= rl.config.Limit
    remaining := rl.config.Limit - count
    if remaining < 0 {
        remaining = 0
    }
    
    return &RateLimitResult{
        Allowed:    allowed,
        Remaining:  remaining,
        ResetTime:  windowEnd,
        RetryAfter: time.Until(windowEnd),
    }, nil
}

// Middleware for rate limiting
func (rl *RateLimiter) Middleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        result, err := rl.CheckLimit(c)
        if err != nil {
            log.Printf("Rate limit check failed: %v", err)
            c.JSON(http.StatusInternalServerError, gin.H{"error": "Rate limit check failed"})
            c.Abort()
            return
        }

        // Set rate limit headers
        c.Header("X-RateLimit-Limit", strconv.Itoa(rl.config.Limit))
        c.Header("X-RateLimit-Remaining", strconv.Itoa(result.Remaining))
        c.Header("X-RateLimit-Reset", strconv.FormatInt(result.ResetTime.Unix(), 10))

        if !result.Allowed {
            c.Header("Retry-After", strconv.Itoa(int(result.RetryAfter.Seconds())))
            c.JSON(http.StatusTooManyRequests, gin.H{
                "error":   "Rate limit exceeded",
                "message": fmt.Sprintf("Too many requests. Try again in %v", result.RetryAfter.Round(time.Second)),
            })
            c.Abort()
            return
        }

        c.Next()
    }
}

// Key generation strategies
func IPBasedKey(c *gin.Context) string {
    return fmt.Sprintf("ip:%s", c.ClientIP())
}

func UserBasedKey(c *gin.Context) string {
    userID := c.GetHeader("X-User-ID")
    if userID == "" {
        userID = "anonymous"
    }
    return fmt.Sprintf("user:%s", userID)
}

func APIKeyBasedKey(c *gin.Context) string {
    apiKey := c.GetHeader("X-API-Key")
    if apiKey == "" {
        return IPBasedKey(c)
    }
    
    // Hash API key for privacy
    hash := sha256.Sum256([]byte(apiKey))
    return fmt.Sprintf("api:%s", hex.EncodeToString(hash[:8]))
}

func EndpointBasedKey(c *gin.Context) string {
    userKey := UserBasedKey(c)
    endpoint := c.FullPath()
    return fmt.Sprintf("%s:%s", userKey, endpoint)
}

// Demo application with multiple rate limiters
func main() {
    client := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })
    defer client.Close()

    r := gin.Default()

    // Global IP-based rate limiter (stricter)
    globalLimiter := NewRateLimiter(client, RateLimitConfig{
        Algorithm:    SlidingWindow,
        Limit:        100, // 100 requests per minute
        Window:       time.Minute,
        KeyGenerator: IPBasedKey,
    })

    // User-based rate limiter (more generous for authenticated users)
    userLimiter := NewRateLimiter(client, RateLimitConfig{
        Algorithm:    TokenBucket,
        Limit:        200,  // 200 requests per minute
        Window:       time.Minute,
        Burst:        50,   // Allow bursts up to 50
        KeyGenerator: UserBasedKey,
    })

    // API key-based rate limiter (highest limits)
    apiLimiter := NewRateLimiter(client, RateLimitConfig{
        Algorithm:    FixedWindow,
        Limit:        1000, // 1000 requests per minute
        Window:       time.Minute,
        KeyGenerator: APIKeyBasedKey,
    })

    // Apply global rate limiting to all routes
    r.Use(globalLimiter.Middleware())

    // Public endpoints (IP-based limiting only)
    r.GET("/public/status", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"status": "ok", "timestamp": time.Now()})
    })

    // User endpoints (additional user-based limiting)
    userGroup := r.Group("/user")
    userGroup.Use(userLimiter.Middleware())
    {
        userGroup.GET("/profile", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"message": "User profile data"})
        })
        
        userGroup.POST("/update", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"message": "Profile updated"})
        })
    }

    // API endpoints (highest limits with API key)
    apiGroup := r.Group("/api/v1")
    apiGroup.Use(apiLimiter.Middleware())
    {
        apiGroup.GET("/data", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"data": "API response"})
        })
        
        apiGroup.POST("/process", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"message": "Processing completed"})
        })
    }

    log.Println("Rate limiting server starting on :8080...")
    r.Run(":8080")
}

Example 2: Security and Fraud Detection System

package main

import (
    "context"
    "crypto/rand"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "log"
    "net"
    "net/http"
    "strconv"
    "strings"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/redis/go-redis/v9"
)

// SecurityEvent represents a security-related event
type SecurityEvent struct {
    Type        string                 `json:"type"`
    IP          string                 `json:"ip"`
    UserID      string                 `json:"user_id,omitempty"`
    Timestamp   time.Time              `json:"timestamp"`
    Details     map[string]interface{} `json:"details"`
    RiskScore   int                    `json:"risk_score"`
    Action      string                 `json:"action"`
}

// ThreatDetector analyzes patterns and detects threats
type ThreatDetector struct {
    client *redis.Client
    ctx    context.Context
}

func NewThreatDetector(client *redis.Client) *ThreatDetector {
    return &ThreatDetector{
        client: client,
        ctx:    context.Background(),
    }
}

// AnalyzeRequest evaluates incoming requests for threats
func (td *ThreatDetector) AnalyzeRequest(c *gin.Context) *SecurityEvent {
    ip := c.ClientIP()
    userAgent := c.GetHeader("User-Agent")
    path := c.Request.URL.Path
    method := c.Request.Method
    
    event := &SecurityEvent{
        Type:      "request_analysis",
        IP:        ip,
        Timestamp: time.Now(),
        Details: map[string]interface{}{
            "user_agent": userAgent,
            "path":       path,
            "method":     method,
        },
        RiskScore: 0,
        Action:    "allow",
    }

    // Check various threat indicators
    event.RiskScore += td.checkIPReputation(ip)
    event.RiskScore += td.checkUserAgent(userAgent)
    event.RiskScore += td.checkRequestPattern(ip, path)
    event.RiskScore += td.checkGeolocation(ip)
    event.RiskScore += td.checkBruteForce(ip, c)

    // Determine action based on risk score
    if event.RiskScore >= 80 {
        event.Action = "block"
    } else if event.RiskScore >= 50 {
        event.Action = "challenge"
    } else if event.RiskScore >= 20 {
        event.Action = "monitor"
    }

    // Log the event
    td.logSecurityEvent(event)

    return event
}

// Check IP reputation
func (td *ThreatDetector) checkIPReputation(ip string) int {
    key := fmt.Sprintf("ip_reputation:%s", ip)
    
    reputation, err := td.client.Get(td.ctx, key).Int()
    if err == redis.Nil {
        return 0 // Unknown IP, neutral score
    } else if err != nil {
        log.Printf("Failed to check IP reputation: %v", err)
        return 0
    }

    // Convert reputation score (0-100) to risk score
    return 100 - reputation
}

// Update IP reputation based on behavior
func (td *ThreatDetector) UpdateIPReputation(ip string, delta int) {
    key := fmt.Sprintf("ip_reputation:%s", ip)
    
    // Use Lua script for atomic update with bounds
    script := `
        local key = KEYS[1]
        local delta = tonumber(ARGV[1])
        local current = tonumber(redis.call('GET', key) or 50)
        local new_score = math.max(0, math.min(100, current + delta))
        redis.call('SET', key, new_score, 'EX', 86400 * 7) -- 7 days expiry
        return new_score
    `
    
    _, err := td.client.Eval(td.ctx, script, []string{key}, delta).Result()
    if err != nil {
        log.Printf("Failed to update IP reputation: %v", err)
    }
}

// Check suspicious user agents
func (td *ThreatDetector) checkUserAgent(userAgent string) int {
    if userAgent == "" {
        return 30 // Missing user agent is suspicious
    }

    // Check for bot patterns
    botPatterns := []string{"bot", "crawler", "spider", "scraper", "curl", "wget"}
    lowerUA := strings.ToLower(userAgent)
    
    for _, pattern := range botPatterns {
        if strings.Contains(lowerUA, pattern) {
            return 40
        }
    }

    // Check for suspicious patterns
    if len(userAgent) < 10 || len(userAgent) > 500 {
        return 25
    }

    return 0
}

// Check request patterns for anomalies
func (td *ThreatDetector) checkRequestPattern(ip, path string) int {
    patternKey := fmt.Sprintf("pattern:%s", ip)
    now := time.Now().Unix()
    
    // Track request frequency and patterns
    script := `
        local key = KEYS[1]
        local now = tonumber(ARGV[1])
        local path = ARGV[2]
        local window = 300 -- 5 minutes
        local window_start = now - window
        
        -- Remove old entries
        redis.call('ZREMRANGEBYSCORE', key, 0, window_start)
        
        -- Add current request
        redis.call('ZADD', key, now, now .. ':' .. path)
        redis.call('EXPIRE', key, window)
        
        -- Count total requests and unique paths
        local total_requests = redis.call('ZCARD', key)
        local all_entries = redis.call('ZRANGE', key, 0, -1)
        local unique_paths = {}
        
        for i, entry in ipairs(all_entries) do
            local request_path = string.match(entry, ':(.+)')
            unique_paths[request_path] = true
        end
        
        local path_count = 0
        for _ in pairs(unique_paths) do
            path_count = path_count + 1
        end
        
        return {total_requests, path_count}
    `
    
    result, err := td.client.Eval(td.ctx, script, []string{patternKey}, now, path).Result()
    if err != nil {
        log.Printf("Failed to check request pattern: %v", err)
        return 0
    }

    res := result.([]interface{})
    totalRequests := int(res[0].(int64))
    uniquePaths := int(res[1].(int64))

    riskScore := 0
    
    // High frequency requests
    if totalRequests > 100 {
        riskScore += 50
    } else if totalRequests > 50 {
        riskScore += 25
    }

    // Too many different paths (scanning behavior)
    if uniquePaths > 20 {
        riskScore += 40
    } else if uniquePaths > 10 {
        riskScore += 20
    }

    return riskScore
}

// Simple geolocation check (normally would use GeoIP database)
func (td *ThreatDetector) checkGeolocation(ip string) int {
    // Check if IP is in suspicious ranges
    suspiciousRanges := []string{
        "10.0.0.0/8",     // Private networks (shouldn't be external)
        "172.16.0.0/12",  // Private networks
        "192.168.0.0/16", // Private networks
    }

    parsedIP := net.ParseIP(ip)
    if parsedIP == nil {
        return 30 // Invalid IP format
    }

    for _, cidr := range suspiciousRanges {
        _, network, err := net.ParseCIDR(cidr)
        if err != nil {
            continue
        }
        
        if network.Contains(parsedIP) {
            return 60 // Suspicious private IP in external request
        }
    }

    return 0
}

// Check for brute force attacks
func (td *ThreatDetector) checkBruteForce(ip string, c *gin.Context) int {
    // Only check login-related endpoints
    path := c.Request.URL.Path
    if !strings.Contains(path, "login") && !strings.Contains(path, "auth") {
        return 0
    }

    key := fmt.Sprintf("bruteforce:%s", ip)
    
    // Count failed attempts in the last hour
    attempts, err := td.client.Get(td.ctx, key).Int()
    if err == redis.Nil {
        return 0
    } else if err != nil {
        return 0
    }

    // Risk score based on failed attempts
    if attempts >= 20 {
        return 80
    } else if attempts >= 10 {
        return 50
    } else if attempts >= 5 {
        return 25
    }

    return 0
}

// Log failed login attempt
func (td *ThreatDetector) LogFailedLogin(ip string) {
    key := fmt.Sprintf("bruteforce:%s", ip)
    
    pipe := td.client.Pipeline()
    pipe.Incr(td.ctx, key)
    pipe.Expire(td.ctx, key, time.Hour)
    _, err := pipe.Exec(td.ctx)
    
    if err != nil {
        log.Printf("Failed to log brute force attempt: %v", err)
    }
}

// Log security events
func (td *ThreatDetector) logSecurityEvent(event *SecurityEvent) {
    eventJSON, err := json.Marshal(event)
    if err != nil {
        log.Printf("Failed to marshal security event: %v", err)
        return
    }

    // Store in Redis list for analysis
    eventsKey := "security_events"
    td.client.LPush(td.ctx, eventsKey, eventJSON)
    td.client.LTrim(td.ctx, eventsKey, 0, 9999) // Keep last 10k events

    // Store IP-specific events
    ipEventsKey := fmt.Sprintf("ip_events:%s", event.IP)
    td.client.LPush(td.ctx, ipEventsKey, eventJSON)
    td.client.Expire(td.ctx, ipEventsKey, 24*time.Hour)

    log.Printf("Security event: IP=%s, Risk=%d, Action=%s", 
               event.IP, event.RiskScore, event.Action)
}

// Security middleware
func (td *ThreatDetector) SecurityMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        event := td.AnalyzeRequest(c)

        switch event.Action {
        case "block":
            c.JSON(http.StatusForbidden, gin.H{
                "error": "Access denied",
                "message": "Your request has been blocked due to suspicious activity",
            })
            c.Abort()
            return

        case "challenge":
            // In a real system, this would trigger CAPTCHA or 2FA
            challengeToken := td.generateChallengeToken()
            c.Header("X-Challenge-Token", challengeToken)
            c.JSON(http.StatusUnauthorized, gin.H{
                "error": "Challenge required",
                "message": "Additional verification required",
                "challenge_token": challengeToken,
            })
            c.Abort()
            return

        case "monitor":
            // Add monitoring headers but allow request
            c.Header("X-Security-Level", "monitored")
        }

        // Store security context for downstream handlers
        c.Set("security_event", event)
        c.Next()
    }
}

func (td *ThreatDetector) generateChallengeToken() string {
    bytes := make([]byte, 16)
    rand.Read(bytes)
    return hex.EncodeToString(bytes)
}

// Demo application with security
func main() {
    client := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })
    defer client.Close()

    detector := NewThreatDetector(client)
    
    r := gin.Default()

    // Apply security middleware
    r.Use(detector.SecurityMiddleware())

    // Login endpoint with brute force protection
    r.POST("/login", func(c *gin.Context) {
        var credentials struct {
            Username string `json:"username"`
            Password string `json:"password"`
        }
        
        if err := c.ShouldBindJSON(&credentials); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
            return
        }

        // Simulate authentication
        if credentials.Username != "admin" || credentials.Password != "password" {
            detector.LogFailedLogin(c.ClientIP())
            detector.UpdateIPReputation(c.ClientIP(), -10) // Decrease reputation
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
            return
        }

        // Successful login
        detector.UpdateIPReputation(c.ClientIP(), 5) // Increase reputation
        c.JSON(http.StatusOK, gin.H{"message": "Login successful"})
    })

    // Protected endpoint
    r.GET("/sensitive", func(c *gin.Context) {
        event := c.MustGet("security_event").(*SecurityEvent)
        c.JSON(http.StatusOK, gin.H{
            "message": "Sensitive data",
            "security_score": event.RiskScore,
        })
    })

    // Security dashboard endpoint
    r.GET("/security/events", func(c *gin.Context) {
        events, err := client.LRange(context.Background(), "security_events", 0, 99).Result()
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch events"})
            return
        }

        c.JSON(http.StatusOK, gin.H{
            "events": events,
            "count":  len(events),
        })
    })

    log.Println("Security-enabled server starting on :8080...")
    r.Run(":8080")
}

6. Hands-On Practice (30-40 minutes)

Exercise 1: Multi-Tier Rate Limiting (15 minutes)

Objective: Implement different rate limits for different user tiers

Requirements:

  • Free tier: 100 requests/hour
  • Premium tier: 1000 requests/hour
  • Enterprise tier: 10000 requests/hour
  • Implement burst allowances and grace periods

Implementation Starter:

type UserTier string

const (
    FreeTier       UserTier = "free"
    PremiumTier    UserTier = "premium"
    EnterpriseTier UserTier = "enterprise"
)

type TierConfig struct {
    Limit      int
    Window     time.Duration
    Burst      int
    GracePeriod time.Duration
}

var TierConfigs = map[UserTier]TierConfig{
    FreeTier:       {Limit: 100, Window: time.Hour, Burst: 10, GracePeriod: 5 * time.Minute},
    PremiumTier:    {Limit: 1000, Window: time.Hour, Burst: 100, GracePeriod: time.Minute},
    EnterpriseTier: {Limit: 10000, Window: time.Hour, Burst: 500, GracePeriod: 30 * time.Second},
}

func TierBasedKey(c *gin.Context) (string, UserTier) {
    // TODO: Implement tier detection from API key or user session
    // TODO: Return appropriate key and tier
}

Exercise 2: DDoS Protection System (15 minutes)

Objective: Build automated DDoS detection and mitigation

Requirements:

  • Detect traffic spikes and unusual patterns
  • Implement automatic IP blocking
  • Create whitelist/blacklist management
  • Add captcha challenge system

Exercise 3: Fraud Detection Pipeline (10 minutes)

Objective: Real-time fraud scoring for transactions

Requirements:

  • Analyze transaction patterns
  • Detect velocity fraud (too many transactions)
  • Implement geolocation-based checks
  • Create risk scoring algorithm

7. Common Pitfalls and Troubleshooting (10 minutes)

Frequent Issues

  1. False Positives in Rate Limiting

    • Problem: Legitimate users getting blocked
    • Solution: Implement grace periods and user feedback mechanisms
  2. Redis Memory Issues with Security Data

    • Problem: Security logs consuming too much memory
    • Solution: Implement TTL and data rotation strategies
  3. Performance Impact of Security Checks

    • Problem: Security middleware slowing down requests
    • Solution: Use Lua scripts and optimize Redis operations

Debugging Tips

// Monitor rate limiting effectiveness
func (rl *RateLimiter) GetStats(key string) map[string]interface{} {
    stats := make(map[string]interface{})
    
    // Get current state
    tokens, _ := rl.client.Get(rl.ctx, fmt.Sprintf("bucket:%s", key)).Int()
    stats["tokens"] = tokens
    
    // Get request history
    history, _ := rl.client.ZRange(rl.ctx, fmt.Sprintf("window:%s", key), 0, -1).Result()
    stats["recent_requests"] = len(history)
    
    return stats
}

// Security event analysis
func (td *ThreatDetector) AnalyzeIP(ip string) map[string]interface{} {
    analysis := make(map[string]interface{})
    
    // Get reputation
    reputation, _ := td.client.Get(td.ctx, fmt.Sprintf("ip_reputation:%s", ip)).Int()
    analysis["reputation"] = reputation
    
    // Get recent events
    events, _ := td.client.LRange(td.ctx, fmt.Sprintf("ip_events:%s", ip), 0, 9).Result()
    analysis["recent_events"] = len(events)
    
    return analysis
}

8. Best Practices and Professional Tips (5 minutes)

Production Security Checklist

  • ✅ Multi-layer Rate Limiting: Different limits for different endpoints and user types
  • ✅ Intelligent Blocking: Use reputation scoring instead of hard blocks
  • ✅ Monitoring and Alerting: Track security metrics and anomalies
  • ✅ Graceful Degradation: System should work even if Redis is down
  • ✅ Regular Security Audits: Review and update security rules
  • ✅ User Communication: Clear messages about rate limits and blocks
  • ✅ Whitelist Management: Easy way to unblock legitimate users

Performance Optimization

// Batch security operations
func (td *ThreatDetector) BatchAnalyze(requests []*http.Request) []*SecurityEvent {
    pipe := td.client.Pipeline()
    
    // Queue all operations
    for _, req := range requests {
        // Add pipeline operations
    }
    
    // Execute all at once
    results, _ := pipe.Exec(td.ctx)
    
    // Process results
    return td.processResults(results)
}

// Use Redis Lua scripts for complex atomic operations
const securityAnalysisScript = `
    -- Complex security analysis in single Redis call
    local ip = ARGV[1]
    local user_agent = ARGV[2]
    -- ... security logic
    return risk_score
`

9. Summary and Review (5 minutes)

Key Takeaways

  1. Rate limiting protects against abuse while maintaining good user experience
  2. Multiple algorithms serve different needs - choose based on requirements
  3. Security is layered - combine multiple detection methods
  4. Redis enables real-time decisions with sub-millisecond performance
  5. Monitoring and tuning are essential for effective security systems

Algorithm Selection Guide

Use Case Best Algorithm Characteristics
API Protection Sliding Window Smooth rate distribution
Burst Traffic Token Bucket Allows bursts, gradual refill
Simple Implementation Fixed Window Easy to understand and debug
High Precision Sliding Window Counter Most accurate, complex

Quick Review Questions

  1. When would you choose token bucket over sliding window?
  2. How do you prevent false positives in security systems?
  3. What Redis data structures are best for rate limiting?
  4. How do you handle distributed rate limiting across multiple servers?
  5. What’s the difference between throttling and blocking?

For Instructors

Teaching Tips

  • Demonstrate actual attacks and how systems respond
  • Show rate limiting in action with load testing tools
  • Use real security event logs to illustrate patterns
  • Demonstrate the cost of security checks on performance
  • Show both over-aggressive and under-protective configurations

Common Student Questions

  • Q: “How do I handle false positives?” A: Implement reputation systems, grace periods, and manual override capabilities

  • Q: “What if Redis goes down?” A: Implement fallback mechanisms - fail open vs fail closed decision

  • Q: “How do I test rate limiting?” A: Use load testing tools like Apache Bench, wrk, or custom scripts

Assessment Ideas

  • Design complete API security architecture
  • Implement custom rate limiting algorithms
  • Build fraud detection system for e-commerce
  • Create DDoS protection for high-traffic application
  • Security audit of existing Redis-based systems

Adaptation Notes

  • Beginners: Focus on basic rate limiting concepts and simple implementations
  • Advanced: Cover sophisticated threat detection and distributed security
  • Enterprise: Emphasize compliance, monitoring, and incident response procedures