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:
-
User Agent Analysis
// Common browser patterns const browserPatterns = [ /Mozilla\/\d+\.\d+/, /Chrome\/\d+\.\d+/, /Safari\/\d+\.\d+/, /Firefox\/\d+\.\d+/, /Edge\/\d+\.\d+/, ]; -
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", ]; -
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:
-
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+/, ]; -
Absence of Browser Headers
- No
sec-fetch-*headers - No
sec-ch-ua*headers - No browser-specific user agent
- No
-
Request Characteristics
- Simple Accept header (
*/*orapplication/json) - Presence of custom API headers
- No cookies (typically)
- Simple Accept header (
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
-
"Access denied" for legitimate browsers
// Check your validation logic browserOnly: { enabled: true, allowMissingHeaders: true, // More permissive blockedUserAgents: [] // Don't block any user agents } -
CLI tools blocked unexpectedly
// Add your CLI tool to allowed list terminalOnly: { enabled: true, allowedUserAgents: [ /MyCLITool\/\d+\.\d+/, /curl\/\d+\.\d+/ ] } -
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.