Skip to content

Authentication & AuthorizationΒΆ

Section: 5-security-architecture
Document: Authentication & Authorization
Status: Comprehensive Implementation Documentation
Audience: Security teams, developers, compliance officers


🎯 Overview¢

MachineAvatars implements a multi-layered authentication and authorization system combining email/password authentication, OTP verification, JWT tokens, role-based access control (RBAC), and enterprise SSO capabilities.

Authentication Methods:

  1. Email/Password (with OTP verification)
  2. reCAPTCHA v2 (bot protection)
  3. Single Sign-On (SSO) - Enterprise only

Authorization:

  • Role-Based Access Control (RBAC)
  • 5 roles with granular permissions
  • Chatbot-level access control
  • Department partitioning (Enterprise)

πŸ”‘ Authentication ArchitectureΒΆ

Multi-Service Authentication FlowΒΆ

graph TB
    subgraph "Client"
        A[Frontend]
    end

    subgraph "Gateway"
        B[Gateway Service<br/>Port 8000/9000]
    end

    subgraph "Authentication Services"
        C[Auth Service<br/>Port 8001<br/>LOGIN ONLY]
        D[User Service<br/>Port 8002<br/>SIGNUP, OTP, RESET]
    end

    subgraph "External Services"
        E[Azure Communication<br/>Email Service]
        F[Google<br/>reCAPTCHA v2]
    end

    subgraph "Database"
        G[(MongoDB<br/>users collection)]
    end

    A -->|1. POST /v2/signup| B
    B --> D
    D -->|2. Send OTP| E
    D -->|3. Create user| G

    A -->|4. POST /v2/verify-otp| B
    B --> D
    D -->|5. Activate user| G

    A -->|6. POST /v2/login| B
    B -->|7. Verify reCAPTCHA| F
    F -->|8. Token valid| B
    B --> C
    C -->|9. Validate credentials| G
    C -->|10. Issue JWT| B
    B -->|11. Return token| A

    style C fill:#FFE082
    style D fill:#E3F2FD

Service ResponsibilitiesΒΆ

Function Auth Service (8001) User Service (8002) Gateway Service
Login βœ… Validate & issue JWT ❌ βœ… reCAPTCHA check
Signup ❌ βœ… Create user + OTP βœ… Forward request
OTP Generation ❌ βœ… 6-digit code ❌
Email Sending ❌ βœ… Azure Communication ❌
OTP Verification ❌ βœ… Verify + activate ❌
Password Reset ❌ βœ… OTP-based reset ❌
JWT Creation βœ… Primary function βœ… Helper function ❌
JWT Validation ❌ ❌ βœ… Token verification

πŸ“§ Email/Password AuthenticationΒΆ

1. Signup FlowΒΆ

Endpoint: POST /v2/signup
Service: User Service (Port 8002)
Response Time: 2-5 seconds (includes email sending)

Complete Signup ProcessΒΆ

sequenceDiagram
    participant C as Client
    participant G as Gateway
    participant U as User Service
    participant DB as MongoDB
    participant AZ as Azure Email

    C->>G: POST /v2/signup<br/>(email, password, name)
    G->>U: Forward request

    Note over U: Step 1: Check existing user
    U->>DB: findOne({email})
    DB-->>U: User or null

    alt User exists & verified
        U-->>C: 400 "Email already registered"
    end

    alt User exists & unverified
        Note over U: Resend OTP flow
        U->>U: generate_otp() β†’ "123456"
        U->>AZ: Send OTP email
        AZ-->>U: Email sent
        U->>DB: Update OTP & expiry
        U-->>C: 200 {token}
    end

    alt New user
        Note over U: Create new user
        U->>U: generate_otp() β†’ "654321"
        U->>AZ: Send OTP email
        AZ-->>U: Email sent
        U->>DB: insertOne(user with OTP)
        U-->>C: 200 {token}
    end

Request FormatΒΆ

const formData = new FormData();
formData.append("email", "user@example.com");
formData.append("password", "mySecurePassword123");
formData.append("name", "John Doe");

const response = await fetch(`${API_URL}/v2/signup`, {
  method: "POST",
  body: formData,
});

Success ResponseΒΆ

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

JWT Payload:

{
    "email": "user@example.com",
    "otp_sent": true,
    "exp": 1735214400  // 30 minutes from now
}

New User Document CreatedΒΆ

{
    "_id": ObjectId("65a1b2c3d4e5f6789abc"),
    "email": "user@example.com",
    "password": "mypassword123",  // ⚠️ PLAIN TEXT - SECURITY ISSUE!
    "name": "John Doe",
    "otp": "654321",
    "otp_expiration": 1735214460,  // Unix timestamp (60 seconds)
    "verified": false,              // Cannot login yet
    "account_status": "paused",     // Activated after OTP verification
    "payment_status": "pending",
    "user_created_at(DATE)": "2025-01-15",
    "subscription_id": "sub_009",   // Free plan by default
    "subscription_date": "2025-01-15"
    // Note: user_id NOT set yet (assigned during OTP verification)
}

2. OTP Verification SystemΒΆ

Endpoint: POST /v2/verify-otp
Service: User Service (Port 8002)
Expiry: 60 seconds

OTP GenerationΒΆ

def generate_otp():
    return ''.join(random.choices("0123456789", k=6))

Characteristics:

  • Length: 6 digits
  • Character Set: 0-9
  • Total Combinations: 1,000,000
  • Expiry: 60 seconds
  • Format: "123456", "987654", etc.

Security Note: With 60-second expiry and 1M combinations, brute force is difficult but not impossible.

Recommendations:

  • βœ… Rate limiting (3 attempts per minute) - NOT IMPLEMENTED
  • βœ… Account lockout after 5 failed attempts - NOT IMPLEMENTED
  • βœ… IP-based blocking for abuse - NOT IMPLEMENTED

Email Delivery (Azure Communication Services)ΒΆ

Configuration:

EMAIL_ENDPOINT = "https://mailing-sevice.india.communication.azure.com/"
EMAIL_ACCESS_KEY = "CgdEWi6fBCJv..." # ⚠️ HARDCODED - SECURITY ISSUE!
SENDER_EMAIL = "DoNotReply@machineagents.ai"

Email Content:

  • Subject: "Your OTP Code"
  • Body: "Your OTP is 123456. It is valid for 60 seconds."
  • Delivery Time: 1-5 seconds
  • Provider: Azure Communication Email (India region)

Improvement Needed:

Welcome to MachineAvatars!

Your verification code is: 123456

This code will expire in 60 seconds.

If you did not request this code, please ignore this email.

---
MachineAvatars Team
https://machine avatars.com

OTP Verification FlowΒΆ

sequenceDiagram
    participant C as Client
    participant U as User Service
    participant DB as MongoDB

    C->>U: POST /v2/verify-otp<br/>(email, otp)

    U->>DB: findOne({email})
    DB-->>U: User document

    alt User not found
        U-->>C: 404 "User not found"
    end

    alt OTP expired
        Note over U: time.time() > otp_expiration
        U-->>C: 400 "OTP expired"
    end

    alt OTP invalid
        Note over U: user["otp"] != otp
        U-->>C: 400 "Invalid OTP"
    end

    alt OTP valid
        Note over U: Generate user_id
        U->>U: user_id = "User-123456"
        U->>DB: updateOne(verified=true, user_id, active)
        Note over U: Assign free plan features
        U->>U: assign_free_plan_features(user_id)
        U->>DB: Insert features_per_user
        U-->>C: 200 {message, user_id}
    end

Request & ResponseΒΆ

Request:

formData.append("email", "user@example.com");
formData.append("otp", "123456");

Success Response:

{
  "message": "OTP verified successfully",
  "user_id": "User-123456"
}

Account ActivationΒΆ

Database Update:

db.users_multichatbot_v2.updateOne(
  { email: "user@example.com" },
  {
    $set: {
      verified: true, // Now can login
      user_id: "User-123456", // Unique identifier
      account_status: "active", // Was "paused"
    },
  }
);

User ID Generation:

user_id = f"User-{random.randint(100000, 999999)}"
# Examples: "User-123456", "User-987654"

Collision Risk: Low but possible (1 in 1 million)

Better Approach:

import time
user_id = f"User-{int(time.time())}_{random.randint(100, 999)}"
# Example: "User-1735214400_456"

3. Login FlowΒΆ

Endpoint: POST /v2/login
Service: Auth Service (Port 8001)
Response Time: 50-100ms

Complete Login ProcessΒΆ

sequenceDiagram
    participant C as Client (Frontend)
    participant G as Gateway
    participant RC as reCAPTCHA v2
    participant A as Auth Service
    participant DB as MongoDB

    C->>C: User enters email, password
    C->>RC: Complete reCAPTCHA
    RC-->>C: reCAPTCHA token

    C->>G: POST /v2/login<br/>(email, password, recaptcha_token)
    G->>RC: Verify reCAPTCHA token
    RC-->>G: Token valid

    alt reCAPTCHA invalid
        G-->>C: 400 "reCAPTCHA validation failed"
    end

    G->>A: Forward login request

    A->>DB: findOne({email})
    DB-->>A: User document or null

    alt User not found
        A-->>C: 404 "User does not exist"
    end

    alt Password mismatch
        Note over A: user["password"] != password<br/>⚠️ PLAIN TEXT COMPARISON!
        A-->>C: 401 "Invalid credentials"
    end

    alt Not verified
        Note over A: verified != true
        A-->>C: 403 "Account not verified"
    end

    alt Login successful
        Note over A: Generate JWT
        A->>A: create_jwt_token({email, user_id})
        A-->>C: 200 {token, user_id}
        C->>C: Store in sessionStorage
        C->>C: Redirect to /chatbots
    end

reCAPTCHA v2 IntegrationΒΆ

Frontend Implementation:

// Configuration
const RECAPTCHA_SITE_KEY = process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY;

// Rendering
window.grecaptcha.render(container, {
  sitekey: RECAPTCHA_SITE_KEY,
  callback: (token: string) => setRecaptchaToken(token),
  "expired-callback": () => setRecaptchaToken(""),
  theme: "dark",
});

Features:

  • βœ… Dark theme
  • βœ… Auto-expiry after 2 minutes
  • βœ… Retry on render failure
  • βœ… Proper cleanup on unmount
  • βœ… Reset on login error

Gateway Verification (Inferred):

# Gateway should verify reCAPTCHA token before forwarding
import requests

def verify_recaptcha(token, secret_key):
    response = requests.post(
        'https://www.google.com/recaptcha/api/siteverify',
        data={
            'secret': secret_key,
            'response': token
        }
    )
    return response.json().get('success', False)

Password ValidationΒΆ

Current Implementation (CRITICAL SECURITY ISSUE):

if user["password"] != password:  # ⚠️ PLAIN TEXT COMPARISON!
    raise HTTPException(status_code=401, detail="Invalid credentials")

Problems:

  1. Passwords stored in plain text in database
  2. Database breach = all passwords exposed
  3. Violates OWASP, GDPR, PCI-DSS, DPDPA standards
  4. No password hashing whatsoever

Required Implementation (bcrypt):

import bcrypt

# On signup (User Service)
password_hash = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt(rounds=12))
users_collection.insert_one({
    "email": email,
    "password_hash": password_hash.decode('utf-8'),  # Store hash, not plain text
    ...
})

# On login (Auth Service)
if not bcrypt.checkpw(password.encode('utf-8'), user["password_hash"].encode('utf-8')):
    raise HTTPException(status_code=401, detail="Invalid credentials")

Migration Plan:

  1. Add password_hash field to schema
  2. Hash existing plain text passwords (one-time script)
  3. Update signup to store hashed passwords
  4. Update login to use bcrypt.checkpw()
  5. Remove password field after migration

4. JWT Token SystemΒΆ

Algorithm: HS256 (HMAC with SHA-256)
Expiry: 30 minutes
Secret: Environment variable JWT_SECRET

Token GenerationΒΆ

def create_jwt_token(data: dict, expires_delta: timedelta = timedelta(minutes=30)):
    to_encode = data.copy()
    expire = datetime.utcnow() + expires_delta
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, JWT_SECRET, algorithm="HS256")

Token StructureΒΆ

Full JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJ1c2VyX2lkIjoiVXNlcl8xMjM0NSIsImV4cCI6MTczNTIxNDQwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

HEADER    . PAYLOAD                                                        . SIGNATURE

Header:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload:

{
  "email": "user@example.com",
  "user_id": "User_12345",
  "exp": 1735214400 // Unix timestamp
}

Signature:

HMACSHA256(
    base64UrlEncode(header) + "." + base64UrlEncode(payload),
    JWT_SECRET
)

Token ValidationΒΆ

Where Validated:

  • Gateway Service (should validate - NOT CONFIRMED)
  • Individual services (should validate before processing)
  • Frontend (checks expiry)

Validation Code (Example):

import jwt
from fastapi import HTTPException, Header

def verify_token(authorization: str = Header(None)):
    if not authorization:
        raise HTTPException(status_code=401, detail="No authorization header")

    try:
        # Remove "Bearer " prefix
        token = authorization.replace("Bearer ", "")

        # Decode and verify
        payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])

        return payload  # {"email": "...", "user_id": "..."}

    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token expired")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Invalid token")

Frontend StorageΒΆ

Session Storage (Current):

// On successful login
sessionStorage.setItem("isLoggedIn", "true");
sessionStorage.setItem("authToken", data.token);
sessionStorage.setItem("user_id", data.user_id);
sessionStorage.setItem("session_id", data.session_id || generateSessionId());
sessionStorage.setItem("userEmail", email);

Usage in Requests:

fetch("https://api.example.com/chatbots", {
  headers: {
    Authorization: `Bearer ${sessionStorage.getItem("authToken")}`,
  },
});

Security Considerations:

  • βœ… sessionStorage (cleared on tab close)
  • ⚠️ Vulnerable to XSS attacks
  • ⚠️ No HttpOnly cookies (more secure alternative)

Recommended: HttpOnly Cookies

// Backend sets cookie
response.set_cookie(
  (key = "auth_token"),
  (value = token),
  (httponly = True), // Not accessible via JavaScript
  (secure = True), // HTTPS only
  (samesite = "strict"), // CSRF protection
  (max_age = 1800) // 30 minutes
);

5. Password Reset FlowΒΆ

3-Step Process:

  1. Request reset (send OTP)
  2. Verify OTP
  3. Set new password

Step 1: Forgot PasswordΒΆ

Endpoint: POST /v2/forgot-password
Service: User Service (Port 8002)

Request:

formData.append("email", "user@example.com");

Process:

  1. Check if user exists
  2. Generate 6-digit OTP
  3. Send OTP via email
  4. Update otp and otp_expiration in database
  5. Return JWT token

Response:

{
  "token": "eyJhbGciOi..."
}

Step 2 & 3: Reset PasswordΒΆ

Endpoint: POST /v2/reset-password
Service: User Service (Port 8002)

Request:

formData.append("email", "user@example.com");
formData.append("otp", "123456");
formData.append("new_password", "newSecurePassword456");

Process:

  1. Validate OTP (same as verify-otp)
  2. Check OTP expiration
  3. Update password in database (⚠️ still plain text!)
  4. Return JWT token

Response:

{
  "token": "eyJhbGciOi..."
}

Complete Flow:

sequenceDiagram
    participant C as Client
    participant U as User Service
    participant DB as MongoDB
    participant E as Azure Email

    Note over C: Step 1: Request Reset
    C->>U: POST /v2/forgot-password<br/>(email)
    U->>DB: findOne({email})
    alt User not found
        U-->>C: 404 "User not found"
    end
    U->>U: generate_otp()
    U->>E: Send OTP email
    U->>DB: Update OTP & expiry
    U-->>C: 200 {token}

    Note over C: Step 2: User receives email
    Note over C: Step 3: Submit new password
    C->>U: POST /v2/reset-password<br/>(email, otp, new_password)
    U->>DB: findOne({email})
    alt OTP invalid/expired
        U-->>C: 400 "Invalid/Expired OTP"
    end
    U->>DB: Update password
    U-->>C: 200 {token}
    C->>C: Redirect to login

πŸ›‘οΈ Authorization & Access ControlΒΆ

Role-Based Access Control (RBAC)ΒΆ

5 Roles Defined:

  1. Owner - Full control over account
  2. Admin - Manage chatbots and users (no billing access)
  3. Editor - Create and edit chatbots
  4. Viewer - Read-only access to chatbots and analytics
  5. Analyst - Access to analytics and reports only

Permissions MatrixΒΆ

Feature Owner Admin Editor Viewer Analyst
Chatbot Management
Create Chatbot βœ… βœ… βœ… ❌ ❌
Edit Chatbot βœ… βœ… βœ… ❌ ❌
Delete Chatbot βœ… βœ… ❌ ❌ ❌
View Chatbot βœ… βœ… βœ… βœ… ❌
Duplicate Chatbot βœ… βœ… βœ… ❌ ❌
User Management
Add Users βœ… βœ… ❌ ❌ ❌
Edit Users βœ… βœ… ❌ ❌ ❌
Remove Users βœ… βœ… ❌ ❌ ❌
Assign Roles βœ… βœ… ❌ ❌ ❌
View Users βœ… βœ… βœ… βœ… ❌
Billing & Subscription
View Billing βœ… ❌ ❌ ❌ ❌
Manage Payment βœ… ❌ ❌ ❌ ❌
Upgrade/Downgrade Plan βœ… ❌ ❌ ❌ ❌
Analytics & Reports
View Analytics βœ… βœ… βœ… βœ… βœ…
Export Data βœ… βœ… ❌ ❌ βœ…
Custom Reports βœ… βœ… ❌ ❌ βœ…
System Prompts
Edit System Prompts βœ… βœ… βœ… ❌ ❌
View Prompts βœ… βœ… βœ… βœ… ❌
Data Management
Upload Files βœ… βœ… βœ… ❌ ❌
Add URLs βœ… βœ… βœ… ❌ ❌
Manage Q&A βœ… βœ… βœ… ❌ ❌
View Knowledge Base βœ… βœ… βœ… βœ… ❌

Chatbot-Level PermissionsΒΆ

Access Control Lists (ACLs):

Users can be granted specific permissions per chatbot:

{
    "chatbot_id": "chatbot_123",
    "permissions": [
        {
            "user_id": "User-456",
            "role": "Editor",
            "granted_by": "User-789",  // Owner
            "granted_at": "2025-01-15T10:00:00Z"
        },
        {
            "user_id": "User-321",
            "role": "Viewer",
            "granted_by": "User-789",
            "granted_at": "2025-01-16T14:30:00Z"
        }
    ]
}

Department Partitioning (Enterprise)ΒΆ

Structure:

Organization: ACME Corp
β”œβ”€β”€ Department: Sales
β”‚   β”œβ”€β”€ User: sales-admin@acme.com (Admin)
β”‚   β”œβ”€β”€ User: sales-rep@acme.com (Editor)
β”‚   β”œβ”€β”€ Chatbot: Product Demo
β”‚   └── Chatbot: Lead Qualifier
β”œβ”€β”€ Department: Support
β”‚   β”œβ”€β”€ User: support-manager@acme.com (Admin)
β”‚   β”œβ”€β”€ User: support-agent@acme.com (Editor)
β”‚   β”œβ”€β”€ Chatbot: Help Center
β”‚   └── Chatbot: Troubleshooting
└── Department: HR
    β”œβ”€β”€ User: hr-manager@acme.com (Admin)
    └── Chatbot: Employee Onboarding

Benefits:

  • Data isolation between departments
  • Independent billing per department
  • Department-specific analytics
  • Role inheritance within department

🏒 Enterprise Single Sign-On (SSO)¢

Availability: Premium Plan only
Status: Implemented

Supported ProtocolsΒΆ

1. SAML 2.0ΒΆ

Identity Providers:

  • Okta
  • OneLogin
  • Azure Active Directory
  • Google Workspace
  • Custom SAML providers

Configuration:

<!-- Service Provider Metadata -->
<EntityDescriptor entityID="https://machineavatars.com/saml">
    <SPSSODescriptor>
        <AssertionConsumerService
            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
            Location="https://machineavatars.com/saml/acs"
            index="0"/>
    </SPSSODescriptor>
</EntityDescriptor>

SAML Flow:

sequenceDiagram
    participant U as User
    participant MA as MachineAvatars
    participant IDP as Identity Provider (Okta)

    U->>MA: Access /login
    MA->>MA: Detect SSO enabled
    MA->>IDP: SAML AuthnRequest
    IDP->>U: Show login page
    U->>IDP: Enter credentials
    IDP->>IDP: Authenticate
    IDP->>MA: SAML Response (Assertion)
    MA->>MA: Validate assertion
    MA->>MA: Create user session
    MA->>U: Redirect to /chatbots

Attributes Mapped:

  • email β†’ User email
  • firstName β†’ User first name
  • lastName β†’ User last name
  • role β†’ RBAC role (if provided)
  • department β†’ Department assignment

2. OAuth 2.0 / OpenID ConnectΒΆ

Identity Providers:

  • Google Workspace
  • Microsoft 365 (Azure AD)
  • GitHub Enterprise

OAuth Flow:

sequenceDiagram
    participant U as User
    participant MA as MachineAvatars
    participant OAuth as OAuth Provider

    U->>MA: Click "Sign in with Google"
    MA->>OAuth: Authorization request
    OAuth->>U: Show consent screen
    U->>OAuth: Grant permission
    OAuth->>MA: Authorization code
    MA->>OAuth: Exchange code for token
    OAuth->>MA: Access token + ID token
    MA->>MA: Validate ID token
    MA->>MA: Create/update user
    MA->>U: Redirect to /chatbots

Configuration Example (Google):

const oauth2Config = {
  client_id: process.env.GOOGLE_CLIENT_ID,
  client_secret: process.env.GOOGLE_CLIENT_SECRET,
  redirect_uri: "https://machineavatars.com/oauth/callback",
  scope: "openid email profile",
  response_type: "code",
};

3. LDAP IntegrationΒΆ

Use Case: On-premise Active Directory

Connection:

import ldap

ldap_server = "ldap://ad.company.com:389"
bind_dn = "CN=ServiceAccount,OU=Services,DC=company,DC=com"
bind_password = "SecurePassword123"

connection = ldap.initialize(ldap_server)
connection.simple_bind_s(bind_dn, bind_password)

Authentication:

def authenticate_ldap(username, password):
    user_dn = f"CN={username},OU=Users,DC=company,DC=com"
    try:
        connection.simple_bind_s(user_dn, password)
        return True
    except ldap.INVALID_CREDENTIALS:
        return False

SSO User ProvisioningΒΆ

Automatic Provisioning:

  1. User logs in via SSO (first time)
  2. MachineAvatars receives user attributes
  3. Check if user exists (by email)
  4. If new: Create user account automatically
  5. If existing: Update user attributes
  6. Assign default role (from SSO or Free plan)
  7. Redirect to dashboard

Just-In-Time (JIT) Provisioning:

  • Users created on first SSO login
  • No pre-provisioning required
  • Attributes synced from IDP

Deprovisioning:

  • User disabled in IDP β†’ session invalidated in MachineAvatars
  • User deleted in IDP β†’ admin notified (manual action required)

πŸ”’ Security Best Practices & RecommendationsΒΆ

Critical Issues to Fix (Priority Order)ΒΆ

1. CRITICAL: Plain Text PasswordsΒΆ

Status: ⚠️ NOT FIXED

Current:

user["password"] != password  # Direct comparison, plain text storage

Required:

import bcrypt

# Signup
password_hash = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt(rounds=12))

# Login
if not bcrypt.checkpw(password.encode('utf-8'), user["password_hash"].encode('utf-8')):
    raise HTTPException(status_code=401)

Migration Steps:

  1. Add password_hash field to users collection
  2. Run migration script to hash existing passwords
  3. Update signup endpoint to use bcrypt
  4. Update login endpoint to use bcrypt.checkpw()
  5. Remove password field after 100% migration

Timeline: IMMEDIATE (Q1 2025)


2. CRITICAL: Hardcoded SecretsΒΆ

Status: ⚠️ NOT FIXED

Problems:

# user-service/src/utils/email_utils.py
EMAIL_ACCESS_KEY = "CgdEWi6fBCJv4c0EHOkq6ZUSML0VQSG49qXgEfrtlLmXY76HV7DeJQQJ99BBACULyCplbknJAAAAAZCSbc3T"

# auth-service/src/main.py
JWT_SECRET = os.getenv("JWT_SECRET", "your_jwt_secret")  # Weak default

Required:

# Environment variables only
EMAIL_ACCESS_KEY = os.getenv("AZURE_EMAIL_KEY")
if not EMAIL_ACCESS_KEY:
    raise ValueError("AZURE_EMAIL_KEY must be set")

JWT_SECRET = os.getenv("JWT_SECRET")
if not JWT_SECRET:
    raise ValueError("JWT_SECRET must be set")

Best Practice: Use Azure Key Vault

Timeline: IMMEDIATE (Q1 2025)


3. HIGH: CORS Allows All OriginsΒΆ

Status: ⚠️ NOT FIXED

Current:

allow_origins=["*"]  # Any website can make requests

Required:

allow_origins=[
    "https://machineavatars.com",
    "https://app.machineavatars.com",
    "http://localhost:3000"  # Dev only
]

Timeline: Q1 2025


4. HIGH: No Rate LimitingΒΆ

Status: ⚠️ NOT IMPLEMENTED

Required:

from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address)

@app.post("/v2/login")
@limiter.limit("5/minute")  # 5 attempts per minute
async def login(...):
    ...

Timeline: Q1 2025


5. MEDIUM: Session Storage (XSS Vulnerable)ΒΆ

Current: sessionStorage (JavaScript accessible)

Recommended: HttpOnly cookies

Timeline: Q2 2025


Additional RecommendationsΒΆ

1. Multi-Factor Authentication (MFA):

  • βœ… Email OTP (implemented)
  • ⏳ SMS OTP (planned)
  • ⏳ Authenticator app (Google Authenticator, Authy)
  • ⏳ Biometric (mobile apps)

2. Session Management:

  • ⏳ Session timeout (30-minute inactivity)
  • ⏳ Concurrent session limits
  • ⏳ Force re-authentication for sensitive actions
  • ⏳ Session invalidation on password change

3. Audit Logging:

  • βœ… Basic logging (login attempts)
  • ⏳ Failed login attempts tracking
  • ⏳ Permission changes logging
  • ⏳ Suspicious activity detection

πŸ“Š Frontend Authentication PatternsΒΆ

Protected RoutesΒΆ

Implementation:

// components/ProtectedLayout.tsx
export function ProtectedLayout({ children }) {
  const router = useRouter();

  useEffect(() => {
    const isLoggedIn = sessionStorage.getItem("isLoggedIn");
    const authToken = sessionStorage.getItem("authToken");

    if (!isLoggedIn || !authToken) {
      router.push("/login");
    }
  }, [router]);

  return <>{children}</>;
}

Usage:

// app/chatbots/page.tsx
export default function ChatbotsPage() {
  return (
    <ProtectedLayout>
      <ChatbotsList />
    </ProtectedLayout>
  );
}

Admin Redirect (Superadmin Feature)ΒΆ

Purpose: Allow superadmin to log in as any user for support

URL Format:

/login?adminRedirect=true&token=xxx&user_id=yyy&session_id=zzz&email=user@example.com

Implementation:

useEffect(() => {
  const urlParams = new URLSearchParams(window.location.search);
  const isAdminRedirect = urlParams.get("adminRedirect");

  if (isAdminRedirect === "true") {
    const token = urlParams.get("token");
    const userId = urlParams.get("user_id");
    const sessionId = urlParams.get("session_id");
    const email = urlParams.get("email");

    if (token && userId) {
      sessionStorage.setItem("isLoggedIn", "true");
      sessionStorage.setItem("authToken", token);
      sessionStorage.setItem("user_id", userId);
      sessionStorage.setItem("session_id", sessionId || generateSessionId());
      sessionStorage.setItem("userEmail", email || "");
      sessionStorage.setItem("connectedFromSuperAdmin", "true"); // Flag for UI

      window.history.replaceState({}, document.title, window.location.pathname);
      router.push("/chatbots");
    }
  }
}, [router]);

Security Considerations:

  • βœ… Token must be valid JWT
  • βœ… URL parameters cleaned from history
  • ⚠️ No IP validation
  • ⚠️ No superadmin session tracking

Auto-Logout on Token ExpiryΒΆ

Current: Manual check on page load

Recommended:

useEffect(() => {
  const checkTokenExpiry = () => {
    const token = sessionStorage.getItem("authToken");
    if (!token) return;

    try {
      const payload = JSON.parse(atob(token.split(".")[1]));
      const exp = payload.exp * 1000; // Convert to milliseconds

      if (Date.now() >= exp) {
        // Token expired
        sessionStorage.clear();
        router.push("/login");
      }
    } catch (error) {
      sessionStorage.clear();
      router.push("/login");
    }
  };

  // Check every minute
  const interval = setInterval(checkTokenExpiry, 60000);
  checkTokenExpiry(); // Check immediately

  return () => clearInterval(interval);
}, [router]);

Backend Services:

Frontend:

Features:

Security:


"Secure authentication is the foundation of trust." πŸ”βœ