Skip to main content

Security Best Practices

Protect your AI APIs with comprehensive security measures

This guide covers authentication, authorization, rate limiting, and other security best practices for deploying NeuroLink server adapters in production.


Authentication

NeuroLink server adapters support multiple authentication strategies out of the box.

Bearer Token Authentication

Bearer tokens (JWT, OAuth tokens) are the most common authentication method for APIs:

import { createServer, createAuthMiddleware } from "@juspay/neurolink";
import jwt from "jsonwebtoken";

const server = await createServer(neurolink, {
framework: "hono",
config: { port: 3000 },
});

server.registerMiddleware(
createAuthMiddleware({
type: "bearer",
validate: async (token, ctx) => {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
return {
id: decoded.sub,
email: decoded.email,
roles: decoded.roles || [],
};
} catch (error) {
return null; // Invalid token
}
},
skipPaths: ["/api/health", "/api/ready"],
errorMessage: "Invalid or expired token",
}),
);

await server.initialize();
await server.start();

API Key Authentication

For service-to-service communication or simple API access:

server.registerMiddleware(
createAuthMiddleware({
type: "api-key",
headerName: "x-api-key", // Default header for api-key type
validate: async (apiKey, ctx) => {
// Validate against your API key store
const keyRecord = await db.apiKeys.findOne({ key: apiKey });

if (!keyRecord || keyRecord.revoked) {
return null;
}

// Check if key is expired
if (keyRecord.expiresAt && new Date() > keyRecord.expiresAt) {
return null;
}

return {
id: keyRecord.userId,
roles: keyRecord.scopes || [],
metadata: {
keyId: keyRecord.id,
rateLimit: keyRecord.rateLimit,
},
};
},
skipPaths: ["/api/health", "/api/ready", "/api/version"],
}),
);

Basic Authentication

For simple username/password authentication:

server.registerMiddleware(
createAuthMiddleware({
type: "basic",
validate: async (credentials, ctx) => {
// credentials is base64 encoded "username:password"
const decoded = Buffer.from(credentials, "base64").toString();
const [username, password] = decoded.split(":");

const user = await db.users.findOne({ username });
if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
return null;
}

return {
id: user.id,
email: user.email,
roles: user.roles,
};
},
skipPaths: ["/api/health"],
}),
);

Custom Authentication

For OAuth 2.0, OIDC, or custom schemes:

server.registerMiddleware(
createAuthMiddleware({
type: "custom",
extractToken: (ctx) => {
// Extract from custom header or query param
return ctx.headers["x-custom-auth"] || ctx.query.token || null;
},
validate: async (token, ctx) => {
// Validate with external auth provider
const response = await fetch("https://auth.example.com/validate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token }),
});

if (!response.ok) {
return null;
}

const user = await response.json();
return {
id: user.sub,
email: user.email,
roles: user.roles,
};
},
skipPaths: ["/api/health", "/api/ready"],
}),
);

Skip Paths Configuration

Certain endpoints should bypass authentication:

const authMiddleware = createAuthMiddleware({
type: "bearer",
validate: validateToken,
skipPaths: [
"/api/health", // Health checks
"/api/ready", // Readiness probes
"/api/version", // Version info
"/api/metrics", // Prometheus metrics (consider securing separately)
"/api/docs", // API documentation
"/api/auth/login", // Login endpoint
"/api/auth/refresh", // Token refresh
],
});

Rate Limiting

Protect your API from abuse with configurable rate limiting.

Basic Configuration

const server = await createServer(neurolink, {
framework: "hono",
config: {
port: 3000,
rateLimit: {
enabled: true,
maxRequests: 100, // 100 requests
windowMs: 60000, // per minute
message: "Too many requests, please try again later",
skipPaths: ["/api/health", "/api/ready"],
},
},
});

Per-IP Rate Limiting

The default behavior limits requests by client IP:

import { createRateLimitMiddleware } from "@juspay/neurolink";

server.registerMiddleware(
createRateLimitMiddleware({
maxRequests: 100,
windowMs: 60000, // 1 minute
keyGenerator: (ctx) => {
// Use X-Forwarded-For when behind a proxy
return (
ctx.headers["x-forwarded-for"]?.split(",")[0].trim() ||
ctx.headers["x-real-ip"] ||
"unknown"
);
},
skipPaths: ["/api/health"],
}),
);

Per-User Rate Limiting

Limit based on authenticated user:

server.registerMiddleware(
createRateLimitMiddleware({
maxRequests: 1000,
windowMs: 3600000, // 1 hour
keyGenerator: (ctx) => {
// Use user ID if authenticated, fall back to IP
return ctx.user?.id || ctx.headers["x-forwarded-for"] || "anonymous";
},
}),
);

Per-API-Key Rate Limiting

Different limits for different API keys:

server.registerMiddleware(
createRateLimitMiddleware({
maxRequests: 100, // Default limit
windowMs: 60000,
keyGenerator: (ctx) => {
const apiKey = ctx.headers["x-api-key"];
return apiKey ? `key:${apiKey}` : `ip:${ctx.headers["x-forwarded-for"]}`;
},
onRateLimitExceeded: (ctx, retryAfter) => {
// Custom response with tier info
return {
error: {
code: "RATE_LIMIT_EXCEEDED",
message: "Rate limit exceeded. Upgrade your plan for higher limits.",
retryAfter,
upgradeUrl: "https://example.com/pricing",
},
};
},
}),
);

Sliding Window Rate Limiting

For smoother rate limiting that prevents burst-and-wait patterns:

import { createSlidingWindowRateLimitMiddleware } from "@juspay/neurolink";

server.registerMiddleware(
createSlidingWindowRateLimitMiddleware({
maxRequests: 100,
windowMs: 60000,
subWindows: 10, // 10 sub-windows for smoother limiting
keyGenerator: (ctx) => ctx.user?.id || ctx.headers["x-forwarded-for"],
}),
);

Rate Limit Headers

Rate limit middleware automatically adds headers to responses:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1706745660

Rate Limit Response Headers

When a request exceeds the rate limit, the server returns HTTP 429 (Too Many Requests) with these headers:

HeaderDescriptionExample
X-RateLimit-LimitMaximum requests per window100
X-RateLimit-RemainingRequests remaining in window0
X-RateLimit-ResetUnix timestamp when limit resets1706745660
Retry-AfterSeconds to wait before retrying60

Clients should respect the Retry-After header to avoid unnecessary requests.


Stream Redaction

Protect sensitive data in streaming responses. Redaction is disabled by default and must be explicitly enabled.

Why Disabled by Default?

Stream redaction is disabled by default because:

  1. It adds processing overhead to every stream chunk
  2. Developers should consciously decide what to redact
  3. Overly aggressive redaction can break functionality

Enabling Stream Redaction

const server = await createServer(neurolink, {
framework: "hono",
config: {
port: 3000,
redaction: {
enabled: true, // Must explicitly enable
// Default redacted fields: apiKey, token, authorization,
// credentials, password, secret, request, args, result
},
},
});

Custom Redaction Configuration

const server = await createServer(neurolink, {
framework: "hono",
config: {
port: 3000,
redaction: {
enabled: true,

// Add custom fields to redact
additionalFields: ["ssn", "creditCard", "bankAccount", "privateKey"],

// Preserve fields that would normally be redacted
preserveFields: ["result"], // Don't redact tool results

// Control tool-specific redaction
redactToolArgs: true, // Redact tool arguments
redactToolResults: false, // Don't redact results

// Custom placeholder
placeholder: "[SENSITIVE DATA REMOVED]",
},
},
});

Programmatic Redaction

For custom streaming routes:

import { createStreamRedactor } from "@juspay/neurolink";

const redactor = createStreamRedactor({
enabled: true,
additionalFields: ["customSecret"],
});

// Use in custom stream handling
for await (const chunk of stream) {
const redactedChunk = redactor(chunk);
response.write(redactedChunk);
}

CORS Configuration

Properly configure Cross-Origin Resource Sharing:

const server = await createServer(neurolink, {
framework: "hono",
config: {
port: 3000,
cors: {
enabled: true,

// Specific origins only (never use "*" in production)
origins: [
"https://myapp.com",
"https://staging.myapp.com",
"https://admin.myapp.com",
],

// Allowed HTTP methods
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],

// Allowed headers
headers: ["Content-Type", "Authorization", "X-API-Key", "X-Request-ID"],

// Allow credentials (cookies, authorization headers)
credentials: true,

// Preflight cache (reduce OPTIONS requests)
maxAge: 86400, // 24 hours
},
},
});

Dynamic CORS Origins

For multi-tenant applications:

// Using Hono's native CORS middleware
import { cors } from "hono/cors";

const app = server.getFrameworkInstance();

app.use(
"/api/*",
cors({
origin: (origin, c) => {
// Validate origin against allowed list
const allowedPattern = /^https:\/\/.*\.myapp\.com$/;

if (allowedPattern.test(origin)) {
return origin;
}

// Check database for custom domains
// (be careful with async operations here)
return null;
},
credentials: true,
}),
);

Security Headers

Add essential security headers to all responses. NeuroLink provides a built-in createSecurityHeadersMiddleware that works with all server adapters (Hono, Express, Fastify, Koa).

The recommended approach is to use NeuroLink's built-in security headers middleware, which works consistently across all frameworks:

import {
createServer,
createSecurityHeadersMiddleware,
} from "@juspay/neurolink";

const server = await createServer(neurolink, {
framework: "hono", // Works with: hono, express, fastify, koa
config: { port: 3000 },
});

server.registerMiddleware(
createSecurityHeadersMiddleware({
contentSecurityPolicy: "default-src 'self'; script-src 'self'",
frameOptions: "DENY",
contentTypeOptions: "nosniff",
hstsMaxAge: 31536000,
referrerPolicy: "strict-origin-when-cross-origin",
customHeaders: {
"X-Custom-Header": "custom-value",
},
}),
);

await server.initialize();
await server.start();

Configuration Options

OptionTypeDefaultDescription
contentSecurityPolicystringundefinedContent-Security-Policy header
frameOptions"DENY" | "SAMEORIGIN" | false"DENY"X-Frame-Options header
contentTypeOptions"nosniff" | false"nosniff"X-Content-Type-Options header
hstsMaxAgenumber | false31536000 (1 year)HSTS max-age in seconds
referrerPolicystring | false"strict-origin-when-cross-origin"Referrer-Policy header
customHeadersRecord<string, string>{}Additional custom headers

Headers Set by the Middleware

The middleware automatically sets these security headers:

HeaderDefault ValuePurpose
X-Frame-OptionsDENYPrevents clickjacking attacks
X-Content-Type-OptionsnosniffPrevents MIME type sniffing
Strict-Transport-Securitymax-age=31536000; includeSubDomainsEnforces HTTPS connections
Referrer-Policystrict-origin-when-cross-originControls referrer information
X-XSS-Protection1; mode=blockXSS filter for older browsers
Content-Security-Policy(only if configured)Controls resource loading

Express Example

import { NeuroLink } from "@juspay/neurolink";
import {
createServer,
createSecurityHeadersMiddleware,
} from "@juspay/neurolink";

const neurolink = new NeuroLink({ defaultProvider: "openai" });

const server = await createServer(neurolink, {
framework: "express",
config: { port: 3000 },
});

server.registerMiddleware(
createSecurityHeadersMiddleware({
contentSecurityPolicy: "default-src 'self'; img-src 'self' data:",
frameOptions: "SAMEORIGIN",
hstsMaxAge: 63072000, // 2 years
}),
);

await server.initialize();
await server.start();

Fastify Example

import { NeuroLink } from "@juspay/neurolink";
import {
createServer,
createSecurityHeadersMiddleware,
} from "@juspay/neurolink";

const neurolink = new NeuroLink({ defaultProvider: "anthropic" });

const server = await createServer(neurolink, {
framework: "fastify",
config: { port: 3000 },
});

server.registerMiddleware(
createSecurityHeadersMiddleware({
frameOptions: "DENY",
referrerPolicy: "no-referrer",
customHeaders: {
"Permissions-Policy": "camera=(), microphone=(), geolocation=()",
},
}),
);

await server.initialize();
await server.start();

Koa Example

import { NeuroLink } from "@juspay/neurolink";
import {
createServer,
createSecurityHeadersMiddleware,
} from "@juspay/neurolink";

const neurolink = new NeuroLink({ defaultProvider: "openai" });

const server = await createServer(neurolink, {
framework: "koa",
config: { port: 3000 },
});

server.registerMiddleware(
createSecurityHeadersMiddleware({
contentSecurityPolicy: "default-src 'self'",
hstsMaxAge: 31536000,
}),
);

await server.initialize();
await server.start();

Hono Example

import { NeuroLink } from "@juspay/neurolink";
import {
createServer,
createSecurityHeadersMiddleware,
} from "@juspay/neurolink";

const neurolink = new NeuroLink({ defaultProvider: "openai" });

const server = await createServer(neurolink, {
framework: "hono",
config: { port: 3000 },
});

server.registerMiddleware(
createSecurityHeadersMiddleware({
contentSecurityPolicy:
"default-src 'self'; script-src 'self' 'unsafe-inline'",
frameOptions: "DENY",
}),
);

await server.initialize();
await server.start();

Disabling Specific Headers

Set any option to false to disable that header:

server.registerMiddleware(
createSecurityHeadersMiddleware({
frameOptions: false, // Disable X-Frame-Options
hstsMaxAge: false, // Disable HSTS
referrerPolicy: false, // Disable Referrer-Policy
}),
);

Framework-Specific Alternatives

If you prefer to use framework-native security middleware, you can access the underlying framework instance:

Using Hono's secureHeaders

import { secureHeaders } from "hono/secure-headers";

const app = server.getFrameworkInstance();

app.use(
"*",
secureHeaders({
contentSecurityPolicy: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
},
xFrameOptions: "DENY",
xContentTypeOptions: "nosniff",
referrerPolicy: "strict-origin-when-cross-origin",
permissionsPolicy: {
camera: [],
microphone: [],
geolocation: [],
},
}),
);

Using Express with Helmet

import helmet from "helmet";

const app = server.getFrameworkInstance();

app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
}),
);

Using Koa with koa-helmet

import helmet from "koa-helmet";

const app = server.getFrameworkInstance();

app.use(helmet());

Production Security Checklist

Authentication

  • Implement authentication middleware
  • Use secure token validation (verify signatures, check expiration)
  • Configure skip paths carefully
  • Implement token refresh mechanism
  • Log authentication failures
  • Implement account lockout after failed attempts

Authorization

  • Implement role-based access control (RBAC)
  • Validate permissions for each endpoint
  • Use principle of least privilege
  • Audit authorization decisions

Rate Limiting

  • Enable rate limiting globally
  • Configure appropriate limits per endpoint type
  • Use sliding window for critical endpoints
  • Implement different tiers for different users
  • Monitor rate limit hits

Data Protection

  • Enable stream redaction for sensitive operations
  • Configure custom fields to redact
  • Validate and sanitize all inputs
  • Encrypt sensitive data at rest
  • Use TLS for all connections

CORS

  • Configure specific allowed origins (no wildcards)
  • Restrict allowed methods and headers
  • Enable credentials only if needed
  • Set appropriate preflight cache

Headers

  • Add Content-Security-Policy
  • Set X-Frame-Options to DENY
  • Enable X-Content-Type-Options
  • Configure Referrer-Policy
  • Add Strict-Transport-Security (HSTS)

Infrastructure

  • Use HTTPS everywhere (terminate at load balancer)
  • Configure firewall rules
  • Use private networking for internal services
  • Implement request timeout
  • Set maximum body size limits
  • Enable access logging
  • Set up intrusion detection

Monitoring

  • Monitor authentication failures
  • Alert on rate limit breaches
  • Track unusual API patterns
  • Log all security events
  • Set up anomaly detection

Security Validation via CLI

Use CLI commands to validate security configuration:

Verify Security Settings

# Check authentication configuration
neurolink server config --get auth

# Check rate limiting settings
neurolink server config --get rateLimit
neurolink server config --get rateLimit.maxRequests

# Check CORS configuration
neurolink server config --get cors
neurolink server config --get cors.enabled

Route Security Audit

# List all routes to verify middleware is applied
neurolink server routes --format json

# Check specific route groups
neurolink server routes --group agent # Verify auth on agent routes
neurolink server routes --group health # Health routes (typically public)

Security Configuration Checklist

SettingCheck CommandRecommended
Rate Limitingserver config --get rateLimit.enabledtrue
Max Requestsserver config --get rateLimit.maxRequests100 per minute
CORSserver config --get cors.enabledtrue in production
CORS Originsserver config --get cors.originsSpecific domains

Hardening Configuration

# Set stricter rate limits for production
neurolink server config --set rateLimit.maxRequests=50
neurolink server config --set rateLimit.windowMs=60000

# Verify changes
neurolink server config --format json

Example: Complete Secure Server

import { NeuroLink } from "@juspay/neurolink";
import {
createServer,
createAuthMiddleware,
createRateLimitMiddleware,
createRoleMiddleware,
} from "@juspay/neurolink";

const neurolink = new NeuroLink({
defaultProvider: "openai",
});

const server = await createServer(neurolink, {
framework: "hono",
config: {
port: 3000,
host: "0.0.0.0",
basePath: "/api",
timeout: 30000,

cors: {
enabled: true,
origins: [process.env.ALLOWED_ORIGIN],
methods: ["GET", "POST"],
headers: ["Content-Type", "Authorization"],
credentials: true,
},

rateLimit: {
enabled: true,
maxRequests: 100,
windowMs: 60000,
skipPaths: ["/api/health", "/api/ready"],
},

bodyParser: {
enabled: true,
maxSize: "1mb",
jsonLimit: "1mb",
},

redaction: {
enabled: true,
additionalFields: ["ssn", "creditCard"],
},
},
});

// Authentication
server.registerMiddleware(
createAuthMiddleware({
type: "bearer",
validate: async (token) => {
// Your JWT validation logic
return validateJWT(token);
},
skipPaths: ["/api/health", "/api/ready", "/api/auth/login"],
}),
);

// Additional rate limit for AI endpoints
server.registerMiddleware({
name: "ai-rate-limit",
order: 6,
paths: ["/api/agent/*"],
handler: async (ctx, next) => {
// Stricter rate limit for AI operations
// Implementation here
return next();
},
});

// Admin-only endpoints
server.registerMiddleware(
createRoleMiddleware({
requiredRoles: ["admin"],
errorMessage: "Admin access required",
}),
);

await server.initialize();
await server.start();

console.log("Secure server running on port 3000");


Need Help? Join our GitHub Discussions or open an issue.