Browser/Terminal Access Control

XyPriss provides sophisticated access control middleware that can restrict endpoints to specific client types. This allows you to create browser-only APIs, terminal-only endpoints, or mixed configurations based on how requests are made.

Overview

Two specialized middleware components allow you to control access based on the request source:

  • BrowserOnly: Blocks requests from non-browser clients (cURL, Postman, scripts, etc.)
  • TerminalOnly: Blocks requests from browsers, allowing only terminal-based clients

Browser-Only Middleware

Use Cases

  • Web Application APIs: Ensure only your frontend can access certain endpoints
  • CSRF Protection: Complement existing CSRF protection
  • User Session Validation: Require browser session cookies
  • Analytics Endpoints: Prevent automated access to analytics data

Quick Start

import { createServer } from "xypriss";

const server = createServer({
  security: {
    browserOnly: true, // Enable globally
  },
});

// This endpoint only accepts browser requests
server.get("/api/user/profile", (req, res) => {
  res.xJson({
    message: "Profile data for browser users only",
    userAgent: req.headers["user-agent"],
  });
});

server.start();

Configuration Options

interface BrowserOnlyConfig {
  /** Enable browser-only access control */
  enabled?: boolean;

  /** Custom error message */
  message?: string;

  /** HTTP status code for blocked requests */
  statusCode?: number;

  /** Additional user agent patterns to allow */
  allowedUserAgents?: RegExp[];

  /** User agent patterns to block */
  blockedUserAgents?: RegExp[];

  /** Require specific headers that browsers typically send */
  requireHeaders?: string[];

  /** Allow requests missing typical browser headers */
  allowMissingHeaders?: boolean;

  /** Custom validation function */
  customValidator?: (req: Request) => boolean;
}

Advanced Configuration

const server = createServer({
  security: {
    browserOnly: {
      enabled: true,
      message: "This endpoint requires a web browser",
      statusCode: 403,
      allowedUserAgents: [
        /CustomBrowser\/\d+\.\d+/, // Allow custom browser
      ],
      blockedUserAgents: [
        /Bot\/\d+\.\d+/, // Block bots
        /Crawler\/\d+\.\d+/, // Block crawlers
      ],
      requireHeaders: ["accept", "accept-language", "cache-control"],
      customValidator: (req) => {
        // Custom validation logic
        return req.headers["x-custom-header"] === "expected-value";
      },
    },
  },
});

Terminal-Only Middleware

Use Cases

  • API Endpoints: Expose APIs only for programmatic access
  • CLI Tools: Create endpoints specifically for command-line tools
  • Internal APIs: Restrict access to server-to-server communication
  • Admin Operations: Allow only terminal-based admin access

Quick Start

const server = createServer({
  security: {
    terminalOnly: true, // Enable globally
  },
});

// This endpoint only accepts terminal requests (cURL, scripts, etc.)
server.post("/api/admin/reset-database", (req, res) => {
  // Dangerous operation - only allow from terminal
  res.xJson({ message: "Database reset complete" });
});

Configuration Options

interface TerminalOnlyConfig {
  /** Enable terminal-only access control */
  enabled?: boolean;

  /** Custom error message */
  message?: string;

  /** HTTP status code for blocked requests */
  statusCode?: number;

  /** Allowed user agent patterns */
  allowedUserAgents?: RegExp[];

  /** Blocked user agent patterns */
  blockedUserAgents?: RegExp[];

  /** Require absence of browser-specific headers */
  blockBrowserHeaders?: string[];

  /** Allow requests with browser-like headers */
  allowBrowserHeaders?: boolean;

  /** Custom validation function */
  customValidator?: (req: Request) => boolean;
}

Advanced Configuration

const server = createServer({
  security: {
    terminalOnly: {
      enabled: true,
      message: "This endpoint is for terminal access only",
      statusCode: 403,
      allowedUserAgents: [
        /curl\/\d+\.\d+/, // Allow cURL
        /HTTPie\/\d+\.\d+/, // Allow HTTPie
        /PostmanRuntime\/\d+\.\d+/, // Allow Postman
        /MyCLITool\/\d+\.\d+/, // Allow custom CLI tool
      ],
      blockedUserAgents: [
        /Mozilla\/\d+\.\d+/, // Block browsers
        /Chrome\/\d+\.\d+/, // Block Chrome
        /Safari\/\d+\.\d+/, // Block Safari
      ],
      blockBrowserHeaders: ["referer", "sec-fetch-mode", "sec-fetch-site"],
      customValidator: (req) => {
        // Custom validation - check for API key
        return req.headers["x-api-key"] === process.env.API_KEY;
      },
    },
  },
});

Detection Logic

Browser Detection

The BrowserOnly middleware identifies browsers by:

  1. User Agent Analysis

    // Common browser patterns
    const browserPatterns = [
      /Mozilla\/\d+\.\d+/,
      /Chrome\/\d+\.\d+/,
      /Safari\/\d+\.\d+/,
      /Firefox\/\d+\.\d+/,
      /Edge\/\d+\.\d+/,
    ];
    
  2. Browser-Specific Headers

    // Headers that browsers typically send
    const browserHeaders = [
      "accept",
      "accept-language",
      "accept-encoding",
      "cache-control",
      "pragma",
      "sec-fetch-mode",
      "sec-fetch-site",
      "sec-fetch-user",
      "sec-fetch-dest",
      "sec-ch-ua",
      "sec-ch-ua-mobile",
      "sec-ch-ua-platform",
    ];
    
  3. Request Characteristics

    • Presence of cookies
    • Referer header
    • DNT (Do Not Track) header
    • Upgrade-Insecure-Requests header

Terminal Detection

The TerminalOnly middleware identifies terminal clients by:

  1. User Agent Analysis

    // Common terminal tool patterns
    const terminalPatterns = [
      /curl\/\d+\.\d+/,
      /wget\/\d+\.\d+/,
      /HTTPie\/\d+\.\d+/,
      /PostmanRuntime\/\d+\.\d+/,
      /python-requests\/\d+\.\d+/,
      /axios\/\d+\.\d+/,
      /node-fetch\/\d+\.\d+/,
    ];
    
  2. Absence of Browser Headers

    • No sec-fetch-* headers
    • No sec-ch-ua* headers
    • No browser-specific user agent
  3. Request Characteristics

    • Simple Accept header (*/* or application/json)
    • Presence of custom API headers
    • No cookies (typically)

Route-Specific Configuration

Apply access control to specific routes:

const server = createServer({
  security: {
    browserOnly: false, // Disabled globally
    terminalOnly: false, // Disabled globally
  },
});

// Browser-only route
server.get("/app/dashboard", (req, res) => {
  // Only accessible from browsers
  res.xJson({ dashboard: "data" });
});

// Terminal-only route
server.post("/api/admin/backup", (req, res) => {
  // Only accessible from terminals
  res.xJson({ backup: "started" });
});

// Mixed access route
server.get("/api/public", (req, res) => {
  // Accessible from both browsers and terminals
  res.xJson({ public: "data" });
});

Integration with Other Security

Combined with Request Signatures

const server = createServer({
  security: {
    // Browser endpoints require session validation
    browserOnly: {
      enabled: true,
      requireHeaders: ["cookie", "authorization"],
    },

    // API endpoints require signature authentication
    terminalOnly: {
      enabled: true,
      customValidator: (req) => {
        return req.headers["x-api-signature"] !== undefined;
      },
    },

    // Request signature for additional security
    requestSignature: {
      secret: "api-secret-key",
    },
  },
});

Combined with CORS

const server = createServer({
  security: {
    cors: {
      origin: [
        "https://myapp.com", // Allow browser access
        /^localhost:\d+$/, // Allow development
      ],
      credentials: true,
    },

    // Additional browser validation
    browserOnly: {
      enabled: true,
      requireHeaders: ["sec-fetch-site"],
    },
  },
});

Practical Examples

Web Application with API

const server = createServer({
  security: {
    // Web app routes - browser only
    browserOnly: {
      enabled: true,
      customValidator: (req) => {
        // Check for session cookie
        return req.headers.cookie?.includes("session_id");
      },
    },
  },
});

// Web app routes
server.get("/app/*", (req, res) => {
  res.sendFile("index.html"); // Serve SPA
});

server.get("/api/user/profile", (req, res) => {
  // Browser-only API
  res.xJson({ profile: getUserProfile(req) });
});

// Public API routes (override browser-only)
server.get("/api/public/stats", (req, res) => {
  // Allow both browser and terminal access
  res.xJson({ stats: getPublicStats() });
});

CLI Tool API

const server = createServer({
  security: {
    // CLI tool routes - terminal only
    terminalOnly: {
      enabled: true,
      allowedUserAgents: [/my-cli-tool\/\d+\.\d+/, /curl\/\d+\.\d+/],
    },
  },
});

// CLI-only endpoints
server.post("/cli/deploy", (req, res) => {
  // Only accessible from CLI tool
  const result = deployApplication(req.body);
  res.xJson({ deployment: result });
});

server.get("/cli/status", (req, res) => {
  // Check deployment status
  res.xJson({ status: getDeploymentStatus() });
});

Admin Panel

const server = createServer({
  security: {
    browserOnly: {
      // Admin panel - browser only with additional checks
      enabled: true,
      customValidator: (req) => {
        const cookies = parseCookies(req.headers.cookie || "");
        return cookies.admin_session && cookies.authenticated === "true";
      },
    },

    terminalOnly: {
      // Admin CLI - terminal only
      enabled: true,
      customValidator: (req) => {
        return req.headers["x-admin-token"] === process.env.ADMIN_TOKEN;
      },
    },
  },
});

// Admin web interface
server.get("/admin/*", (req, res) => {
  res.sendFile("admin.html");
});

// Admin API (browser)
server.get("/admin/api/users", (req, res) => {
  res.xJson({ users: getAllUsers() });
});

// Admin CLI commands (terminal)
server.post("/admin/cli/reset-passwords", (req, res) => {
  resetAllPasswords();
  res.xJson({ message: "All passwords reset" });
});

Security Considerations

1. User Agent Spoofing

// User agents can be spoofed - don't rely solely on them
const server = createServer({
  security: {
    browserOnly: {
      enabled: true,
      // Use multiple validation methods
      requireHeaders: ["sec-fetch-site", "sec-fetch-mode"],
      customValidator: (req) => {
        // Check for browser-specific behavior
        return hasBrowserCharacteristics(req);
      },
    },
  },
});

2. Header Manipulation

// Headers can be forged - use multiple signals
const server = createServer({
  security: {
    terminalOnly: {
      enabled: true,
      // Combine multiple checks
      allowedUserAgents: [/curl\/\d+\.\d+/],
      blockBrowserHeaders: ["sec-fetch-mode"],
      customValidator: (req) => {
        // Additional validation
        return req.headers["x-cli-version"] !== undefined;
      },
    },
  },
});

3. False Positives

// Some legitimate clients may be misidentified
const server = createServer({
  security: {
    browserOnly: {
      enabled: true,
      allowMissingHeaders: true, // Be more permissive
      blockedUserAgents: [
        /Bot\/\d+\.\d+/, // Only block obvious bots
        /Crawler\/\d+\.\d+/,
      ],
    },
  },
});

Performance Impact

  • Minimal overhead: Simple header and user agent checks
  • Fast validation: Regex patterns are pre-compiled
  • Early termination: Fails fast on invalid requests
  • Memory efficient: No external dependencies

Troubleshooting

Common Issues

  1. "Access denied" for legitimate browsers

    // Check your validation logic
    browserOnly: {
      enabled: true,
      allowMissingHeaders: true, // More permissive
      blockedUserAgents: []      // Don't block any user agents
    }
    
  2. CLI tools blocked unexpectedly

    // Add your CLI tool to allowed list
    terminalOnly: {
      enabled: true,
      allowedUserAgents: [
        /MyCLITool\/\d+\.\d+/,
        /curl\/\d+\.\d+/
      ]
    }
    
  3. Mobile browsers misidentified

    // Mobile browsers have different characteristics
    browserOnly: {
      enabled: true,
      requireHeaders: [], // Don't require desktop-specific headers
      customValidator: (req) => {
        const ua = req.headers['user-agent'] || '';
        return /Mobile|Android|iPhone|iPad/.test(ua) ||
               req.headers['sec-ch-ua-mobile'] === '?1';
      }
    }
    

Debug Mode

Enable detailed logging:

const server = createServer({
  logging: {
    level: "debug",
    components: {
      security: true,
    },
  },
  security: {
    browserOnly: {
      enabled: true,
      debug: true, // Enable debug logging
    },
  },
});

This will log:

[BROWSER_CHECK] User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
[BROWSER_CHECK] Headers present: accept, accept-language, sec-fetch-mode
[BROWSER_CHECK] Validation passed: true

Migration Guide

From Custom Middleware

// Old approach
server.use("/api/web", (req, res, next) => {
  const ua = req.headers["user-agent"] || "";
  if (!ua.includes("Mozilla")) {
    return res.status(403).json({ error: "Browser required" });
  }
  next();
});

// New approach
const server = createServer({
  security: {
    browserOnly: true,
  },
});

From IP-Based Restrictions

// Old approach (less reliable)
server.use("/admin", (req, res, next) => {
  const clientIP = req.ip;
  if (!adminIPs.includes(clientIP)) {
    return res.status(403).json({ error: "Access denied" });
  }
  next();
});

// New approach (more reliable)
const server = createServer({
  security: {
    browserOnly: {
      enabled: true,
      customValidator: (req) => {
        // Combine IP check with browser validation
        const clientIP = req.ip;
        return adminIPs.includes(clientIP) && isBrowser(req);
      },
    },
  },
});

Browser Compatibility

Supported Browsers

  • ✅ Chrome 77+ (sends sec-fetch-* headers)
  • ✅ Firefox 68+ (sends sec-fetch-* headers)
  • ✅ Safari 12.1+ (sends sec-fetch-* headers)
  • ✅ Edge 79+ (Chromium-based)

Legacy Browser Support

// Fallback for older browsers
const server = createServer({
  security: {
    browserOnly: {
      enabled: true,
      requireHeaders: [], // Don't require modern headers
      customValidator: (req) => {
        const ua = req.headers["user-agent"] || "";
        // Fallback to user agent checking
        return /Mozilla|Chrome|Safari|Firefox|Edge/.test(ua);
      },
    },
  },
});

Integration Examples

SPA with API Separation

// Frontend routes - browser only
server.get("/app/*", (req, res) => {
  res.sendFile("index.html");
});

// API routes - mixed access
server.get("/api/public", (req, res) => {
  // Allow both browsers and terminals
  res.xJson({ data: "public" });
});

// Admin routes - browser only with session validation
server.get("/admin/*", (req, res) => {
  // Additional session validation
  res.sendFile("admin.html");
});

Microservices Communication

// Service A (web app) - browser only
const webApp = createServer({
  security: { browserOnly: true },
});

// Service B (API) - terminal only (server-to-server)
const apiService = createServer({
  security: { terminalOnly: true },
});

// Service C (CLI) - terminal only
const cliService = createServer({
  security: { terminalOnly: true },
});

Browser/Terminal access control provides fine-grained control over who can access your endpoints. By distinguishing between browser-based users and programmatic clients, you can create more secure and appropriate APIs for different use cases.