InfraForge Docs

InfraNotes Module · v0

Welcome

Select a document from the sidebar to read it.

NATS Integration Guide for Authentication

Deprecated – historical NATS design

InfraNotes has fully migrated away from NATS. The financial analytics module now integrates with InfraNotes via HTTP APIs secured with shared JWT authentication, and the InfraNotes platform uses Kafka as its event bus. This document is kept only for historical reference and must not be used as a guide for new work.

This document outlines the previous integration between InfraNotes (main application) and InfraNotes-Module (financial analytics module) using NATS for authentication and authorization.

Overview

The integration will use NATS as a message broker to:

  1. Validate authentication tokens between services
  2. Synchronize user permissions
  3. Propagate authentication events (login, logout, etc.)
  4. Enable secure communication between services

Current Implementation

InfraNotes (Main Application)

The main application currently handles authentication with:

  • JWT-based authentication with access and refresh tokens
  • Role-based authorization (admin, user)
  • Session management with device tracking
  • MFA support and email verification
  • Token blacklisting for security

InfraNotes-Module (Financial Analytics)

The module currently has:

  • Placeholder authentication in internal/common/auth/auth.go
  • Development mode that uses a default user ID

NATS Integration Architecture

┌────────────────┐                 ┌────────────────┐
│                │                 │                │
│   InfraNotes   │◄───NATS Bus────►│ InfraNotes     │
│   (Main App)   │                 │ Module         │
│                │                 │                │
└────────────────┘                 └────────────────┘
        ▲                                  ▲
        │                                  │
        │                                  │
        ▼                                  ▼
┌────────────────┐                 ┌────────────────┐
│                │                 │                │
│   Database     │                 │   Database     │
│                │                 │                │
└────────────────┘                 └────────────────┘

Message Subjects

Subject Pattern Description Direction
auth.validate.token Validate a JWT token Module → Main
auth.validate.user.<user_id> Validate user permissions Module → Main
auth.event.login User login notification Main → Module
auth.event.logout User logout notification Main → Module
auth.event.token.revoked Token revocation notification Main → Module

Message Formats

Token Validation Request

{
  "token": "jwt_token_string",
  "request_id": "uuid_for_request_tracking"
}

Token Validation Response

{
  "valid": true,
  "user_id": "user_uuid",
  "user_role": "user",
  "permissions": ["read:analytics", "write:documents"],
  "session_id": "session_uuid",
  "request_id": "uuid_from_request",
  "error": ""
}

User Validation Request

{
  "user_id": "user_uuid",
  "resource": "analytics/documents",
  "action": "read",
  "request_id": "uuid_for_request_tracking"
}

User Validation Response

{
  "permitted": true,
  "user_id": "user_uuid",
  "request_id": "uuid_from_request",
  "error": ""
}

Implementation Guidelines

For InfraNotes (Main App)

  1. NATS Connection Setup
// Connect to NATS
nc, err := nats.Connect("nats://nats:4222", nats.Name("infranotes-main"))
if err != nil {
    log.Fatalf("Failed to connect to NATS: %v", err)
}
  1. Authentication Validation Endpoints
// Subscribe to token validation requests
nc.Subscribe("auth.validate.token", func(msg *nats.Msg) {
    var req TokenValidationRequest
    if err := json.Unmarshal(msg.Data, &req); err != nil {
        // Handle error
        return
    }
    
    // Validate token using existing AuthService
    userID, role, sessionID, err := authService.VerifyToken(req.Token)
    
    // Prepare and send response
    resp := TokenValidationResponse{
        Valid:     err == nil,
        UserID:    userID,
        UserRole:  role,
        SessionID: sessionID,
        RequestID: req.RequestID,
    }
    
    if err != nil {
        resp.Error = err.Error()
    }
    
    respData, _ := json.Marshal(resp)
    msg.Respond(respData)
})
  1. Event Publishing
// Publish authentication events
func publishAuthEvent(nc *nats.Conn, eventType string, data interface{}) error {
    eventData, err := json.Marshal(data)
    if err != nil {
        return err
    }
    return nc.Publish("auth.event."+eventType, eventData)
}

For InfraNotes-Module

  1. NATS Connection Setup
// Connect to NATS
nc, err := nats.Connect("nats://nats:4222", nats.Name("infranotes-module"))
if err != nil {
    log.Fatalf("Failed to connect to NATS: %v", err)
}
  1. Authentication Middleware
// NatsAuthMiddleware validates authentication with the main app via NATS
func NatsAuthMiddleware(nc *nats.Conn) echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            // Get Authorization header
            authHeader := c.Request().Header.Get("Authorization")
            if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
                return echo.NewHTTPError(http.StatusUnauthorized, "Invalid or missing Authorization header")
            }
            
            // Extract token
            token := strings.TrimPrefix(authHeader, "Bearer ")
            
            // Create validation request
            req := TokenValidationRequest{
                Token:     token,
                RequestID: uuid.New().String(),
            }
            
            // Request validation from main app
            reqData, _ := json.Marshal(req)
            msg, err := nc.Request("auth.validate.token", reqData, 5*time.Second)
            if err != nil {
                return echo.NewHTTPError(http.StatusInternalServerError, "Authentication service unavailable")
            }
            
            // Parse response
            var resp TokenValidationResponse
            if err := json.Unmarshal(msg.Data, &resp); err != nil {
                return echo.NewHTTPError(http.StatusInternalServerError, "Invalid response from auth service")
            }
            
            // Check if token is valid
            if !resp.Valid {
                return echo.NewHTTPError(http.StatusUnauthorized, resp.Error)
            }
            
            // Set user ID in context
            userID, _ := uuid.Parse(resp.UserID)
            auth.SetUserIDToContext(c, userID)
            
            // Continue to next handler
            return next(c)
        }
    }
}
  1. Event Subscription
// Subscribe to auth events for token revocation
nc.Subscribe("auth.event.token.revoked", func(msg *nats.Msg) {
    var event TokenRevokedEvent
    if err := json.Unmarshal(msg.Data, &event); err != nil {
        log.Printf("Error parsing token revoked event: %v", err)
        return
    }
    
    // Update local token blacklist or cache
    tokenBlacklistService.AddToken(event.Token, event.Reason)
})

Data Models

// TokenValidationRequest represents a request to validate a token
type TokenValidationRequest struct {
    Token     string `json:"token"`
    RequestID string `json:"request_id"`
}

// TokenValidationResponse represents the response to a token validation
type TokenValidationResponse struct {
    Valid       bool     `json:"valid"`
    UserID      string   `json:"user_id"`
    UserRole    string   `json:"user_role"`
    Permissions []string `json:"permissions"`
    SessionID   string   `json:"session_id"`
    RequestID   string   `json:"request_id"`
    Error       string   `json:"error,omitempty"`
}

// TokenRevokedEvent represents a token revocation event
type TokenRevokedEvent struct {
    Token        string    `json:"token"`
    UserID       string    `json:"user_id"`
    RevokedAt    time.Time `json:"revoked_at"`
    Reason       string    `json:"reason,omitempty"`
    SessionID    string    `json:"session_id,omitempty"`
    RevokedByApp string    `json:"revoked_by_app"`
}

Docker Compose Updates

Both applications will need access to the NATS server. Update the docker-compose.yml files to include:

services:
  nats:
    image: nats:latest
    ports:
      - "4222:4222"
      - "8222:8222"
    volumes:
      - ./configs/nats:/etc/nats
    command: "--config /etc/nats/nats.conf"

Error Handling and Fallbacks

  1. If NATS connection fails:

    • Log the error
    • Use fallback authentication method (e.g., direct HTTP call)
    • Retry connection with exponential backoff
  2. If validation request times out:

    • Cache validation results to reduce dependency on NATS
    • Use circuit breaker pattern to prevent cascading failures

Security Considerations

  1. Ensure NATS connections use TLS in production
  2. Implement proper NATS authentication (username/password or certificates)
  3. Keep token validation responses minimal (only include necessary data)
  4. Add rate limiting for authentication requests
  5. Monitor for suspicious validation patterns

Testing

  1. Unit tests for NATS message format validation
  2. Integration tests between services
  3. Fault injection testing (NATS server down scenarios)
  4. Performance testing under load
  5. Security testing (attempt to bypass authentication)

Deployment Plan

  1. Develop and test in local environment
  2. Deploy NATS server
  3. Update InfraNotes with publisher implementation
  4. Update InfraNotes-Module with subscriber implementation
  5. Verify integration end-to-end
  6. Monitor and adjust as needed