A complete guideline for integrating third-party APIs safely in a Node.js/TypeScript backend using Express, OAuth 2.0 and JWT.
Your Node.js backend is only as secure as the third-party APIs you integrate. Every external service introduces new attack vectors, authentication complexities, and potential data breaches. Here's how to build bulletproof API integrations that protect your users and your business.
When you integrate external APIs, you're essentially extending your security perimeter to include systems you don't control. Each integration creates potential vulnerabilities:
A single compromised API integration can expose your entire application. Traditional security measures often miss these integration-specific vulnerabilities.
These Cursor Rules implement a comprehensive security-first approach to third-party API integration. Built on real-world enterprise patterns, they enforce OAuth 2.0 authentication, centralized API gateway control, and defense-in-depth strategies that protect against both common and sophisticated attacks.
The rules establish secure coding patterns that prevent vulnerabilities at the source while maintaining developer productivity through clear TypeScript interfaces and streamlined authentication flows.
Zero Secret Exposure: Automatic secret management integration with AWS Secrets Manager and HashiCorp Vault ensures credentials never touch your codebase.
Defense in Depth: Multi-layer security with API gateways, request validation, rate limiting, and circuit breakers protecting against various attack vectors.
Secure by Default: Every API call includes authentication, authorization, input validation, and structured error handling with security logging.
OAuth 2.0 Compliance: Proper implementation of Authorization Code Flow with PKCE, token rotation, and scope validation prevents authentication bypass attacks.
Production-Ready Monitoring: Comprehensive logging, metrics, and alerting for detecting and responding to security incidents in real-time.
Before these rules, developers often write vulnerable integration code:
// Vulnerable approach
app.get('/user', async (req, res) => {
const response = await fetch('https://api.github.com/user', {
headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` }
});
res.json(await response.json()); // Exposes GitHub errors directly
});
With the security rules, the same integration becomes bulletproof:
// Secure implementation
const router = Router();
router.get("/github/profile",
authScope("read:github"),
validateGitHubHeaders,
async (req, res, next) => {
try {
const profile = await getUserProfile(res.locals.accessToken);
res.json(profile);
} catch (err) {
next(err); // Handled by global error middleware
}
}
);
// Typed service wrapper
interface GitHubUser { id: number; login: string; }
export async function getUserProfile(token: string): Promise<GitHubUser> {
const res = await githubClient.get<GitHubUser>("/user", {
headers: { Authorization: `Bearer ${token}` }
});
return res.data;
}
Instead of managing environment variables manually:
// Traditional approach - vulnerable
const apiKey = process.env.STRIPE_API_KEY; // Exposed in logs, CI/CD
// Secure approach - secrets injected at runtime
const secretsManager = new AWS.SecretsManager();
const apiKey = await secretsManager.getSecretValue({
SecretId: 'stripe-api-key'
}).promise();
Prevent cascade failures when external APIs become unreliable:
import CircuitBreaker from 'opossum';
const breaker = new CircuitBreaker(externalApiCall, {
timeout: 3000,
errorThresholdPercentage: 50,
resetTimeout: 30000
});
breaker.fallback(() => ({ status: 'degraded', data: null }));
Create the secure directory structure:
mkdir -p src/{config,middlewares,services,routes,utils,types,tests}
Update your tsconfig.json:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"esModuleInterop": true
}
}
npm install express helmet cors express-rate-limit express-jwt
npm install axios opossum pino zod
npm install --save-dev @types/node @types/express nock
Configure your API gateway with request validation and rate limiting:
# api-gateway-config.yml
policies:
- name: "rate-limit"
config:
requests_per_minute: 1000
- name: "request-validation"
config:
validate_request_body: true
validate_request_uri: true
// middlewares/security.ts
import helmet from 'helmet';
import cors from 'cors';
import rateLimit from 'express-rate-limit';
export const securityMiddleware = [
helmet(),
cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') }),
express.json({ limit: '1mb' }),
rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
})
];
Set up AWS Secrets Manager or HashiCorp Vault integration:
// config/secrets.ts
import { SecretsManager } from 'aws-sdk';
const secretsManager = new SecretsManager();
export async function getSecret(secretName: string): Promise<string> {
const result = await secretsManager.getSecretValue({
SecretId: secretName
}).promise();
return result.SecretString!;
}
These rules transform your API integration approach from reactive security patching to proactive security by design. Your development team gains productivity while your security posture becomes enterprise-grade.
You are an expert in Node.js, TypeScript, Express, OAuth 2.0, OpenID Connect, JWT, TLS, Amazon API Gateway, Apigee, AWS Secrets Manager, HashiCorp Vault.
Key Principles
- Prioritise security by design: authentication, authorisation, transport-layer encryption and least-privilege permissions are mandatory for every request.
- Centralise control behind an API gateway for policy enforcement, rate limiting, logging, and throttling.
- Never embed secrets in client code; store them in dedicated secret-management services and inject at runtime.
- Validate and sanitise all external data (headers, body, query, path) on both ingress and egress to avoid injection and data-exfiltration attacks.
- Fail securely: deny by default, reveal no sensitive implementation details and log every rejected request.
- Keep dependencies current; schedule automated dependency & security scans.
TypeScript (Node.js)
- Enable `strict`, `noImplicitAny`, `strictNullChecks`, `esModuleInterop` in `tsconfig.json`.
- All functions, middleware and util helpers must define explicit parameter and return types; avoid `any`.
- Export a single default object/function per file; file naming: `kebab-case.ts`.
- Prefer `async/await` over callbacks/promises chains; wrap every awaited call in `try/catch`.
- Use `axios` with a typed API client wrapper:
```ts
interface GitHubUser { id: number; login: string; }
export async function fetchGitHubUser(token: string): Promise<GitHubUser> {
const res = await githubClient.get<GitHubUser>("/user", { headers: { Authorization: `Bearer ${token}` } });
return res.data;
}
```
- Use enums/constants for external HTTP status codes to avoid magic numbers.
- Do not log raw tokens; redact with helper `redactToken(token)`.
Error Handling and Validation
- First line in every route/middleware: schema validation via `zod` or `@hapi/joi`.
- Use early returns; maximum nesting level should be 2.
- Standardise error objects `{ status:number; code:string; message:string; correlationId:string }`.
- Map external API errors to internal error codes; never forward third-party error bodies to clients.
- Implement a global `errorHandler` middleware that:
1. Translates error objects to HTTP responses.
2. Masks stack traces in production.
3. Emits structured logs (`pino`) with correlationId.
- Reject requests beyond configured rate limits (`express-rate-limit`). Respond with HTTP 429.
Express Framework Rules
- Use stateless JWT bearer tokens validated with `express-jwt` or `passport-jwt`; verify signature, issuer, audience, expiry.
- Mount security middleware in this order:
1. `helmet()` for secure headers.
2. `cors()` with explicit origin allow-list.
3. `express.json({ limit: "1mb" })`.
4. `rateLimiter` (shared Redis store for distributed workloads).
5. `authMiddleware` (JWT/OAuth scopes check).
- Keep route files thin; delegate business logic to services.
- Use dependency-injection (e.g. `tsyringe`) to facilitate mocking in tests.
- Graceful shutdown: listen for `SIGTERM`, finish in-flight requests, close DB connections.
OAuth 2.0 / JWT Rules
- Use Authorisation Code Flow with PKCE for public clients.
- Access tokens: 15 min expiry; refresh tokens: 24 h; store refresh tokens encrypted at rest.
- Sign JWTs with RS256 keys rotated every 30 days. Maintain JWKS endpoint.
- Embed minimal claims (`sub`, `scope`, `exp`, `iat`, `iss`, `aud`); avoid PII.
- Validate `exp`, `nbf`, clock-skew ≤ 60 s.
API Gateway Rules
- Deploy third-party APIs behind Amazon API Gateway/Apigee for:
• Request validation (JSON Schema).
• Dynamic throttling (burstable & sustained).
• IP allow/deny lists.
• WAF integration for OWASP Top 10.
- Enable access logs in JSON to CloudWatch/Stackdriver with unique request IDs.
Testing & Auditing
- Unit-test every integration wrapper with nock/mock-service-worker; cover both success and error branches.
- Weekly dependency scan with `npm audit`, `snyk test`.
- Quarterly penetration test; fix CVSS > 4 within 30 days.
- Include security regression tests in CI; block merge on severity high.
Performance & Reliability
- Wrap external calls in circuit breakers (`opossum`) with fallback responses.
- Configure 3-try exponential back-off (`500 ms / 1 s / 2 s`).
- Cache idempotent GET responses in Redis for 30–60 seconds to reduce rate usage.
Logging & Monitoring
- Use structured logs (`pino`, JSON) with fields: `timestamp`, `level`, `msg`, `correlationId`, `userId`, `provider`.
- Forward logs to central system (ELK, CloudWatch Logs).
- Expose `/healthz` and `/readyz` endpoints; integrate with Prometheus; publish metrics: `request_latency`, `rate_limit_hits`, `jwt_validation_failures`.
Secret Management
- Store API keys, client secrets, and signing keys in AWS Secrets Manager or HashiCorp Vault.
- Grant IAM roles specific read access (least privilege).
- Rotate credentials automatically every 90 days; notify on rotation failure.
- Never commit `.env` or secret files to VCS; enforce with pre-commit hooks (`git-secrets`).
Directory Structure
```
src/
config/ # env parsing & configuration
middlewares/ # auth, rate-limit, validation, error handler
services/ # business & external API clients
routes/ # express routers only
utils/ # shared helpers (e.g., redactToken)
types/ # global .d.ts or interfaces
tests/ # unit & contract tests
```
Common Pitfalls & Remedies
- Pitfall: Hard-coding base URLs or API versions.
Remedy: Keep in config with environment-specific overrides.
- Pitfall: Ignoring 429/5xx back-off advisories.
Remedy: Inspect `Retry-After` header; back-off accordingly.
- Pitfall: Oversharing error details.
Remedy: Map errors, drop stack traces, supply generic message.
Deploy & CI/CD
- Block merge on failing lint (`eslint --max-warnings 0`) or tests.
- Run `npm ci --ignore-scripts` in CI to ensure lockfile fidelity.
- Use OWASP Dependency-Check in build pipeline.
- Enforce signed commits and branch protection.
Reference Example (Simplified Route)
```ts
import { Router } from "express";
import { getUserProfile } from "../services/github.service";
import { validateGitHubHeaders } from "../middlewares/validation";
import { authScope } from "../middlewares/auth";
const router = Router();
router.get("/github/profile", authScope("read:github"), validateGitHubHeaders, async (req, res, next) => {
try {
const profile = await getUserProfile(res.locals.accessToken);
res.json(profile);
} catch (err) {
next(err);
}
});
export default router;
```