Api Key Exposure in Express with Bearer Tokens
Api Key Exposure in Express with Bearer Tokens — how this specific combination creates or exposes the vulnerability
In Express applications, storing or transmitting API keys as Bearer tokens can expose secrets through multiple vectors when security hygiene is inconsistent. A common pattern is to pass the key via the Authorization header as Authorization: Bearer <token>. If the server includes the raw token in logs, error messages, or response headers, the key is effectively exposed to anyone who can access those outputs. For example, logging the full authorization header without redaction creates a persistent record that can be leaked through log aggregation systems or compromised log viewers.
Another exposure path arises when the Express app constructs URLs or redirects using query parameters instead of the Authorization header. Query strings are often stored in server logs, browser history, and proxy logs, so a key passed as ?api_key=sk_xxx or as a Bearer token moved into a query parameter can persist beyond the immediate request. MiddleBrick’s scans detect data exposure findings when API endpoints inadvertently echo the token in responses or when error handlers surface stack traces that include authorization values.
CORS misconfiguration can compound the issue. If an Express server responds to preflight requests with overly broad origins or exposes headers like Authorization via Access-Control-Expose-Headers, client-side JavaScript may read the presence or value of the Bearer token. This expands the exposure from server-side logs to the browser environment, increasing the risk of token theft via cross-origin requests or malicious browser extensions.
It’s also important to consider how the token is generated and rotated. Hardcoded keys in source files or configuration files checked into version control represent a static secret that does not change over time. Automated scans can detect these patterns by correlating runtime behavior with known secret formats (e.g., high-entropy strings beginning with sk_live_ or pk_test_). Without short-lived tokens or a rotation strategy, any exposure event has a long window of impact.
Finally, the API’s design should avoid relying on Bearer tokens in contexts where they cannot be protected. For example, embedding keys in JavaScript bundles that are served to browsers makes extraction trivial. MiddleBrick’s unauthenticated scan can identify endpoints that accept Bearer tokens but lack transport security or proper referrer checks, highlighting the need to restrict token usage to server-to-server communication wherever feasible.
Bearer Tokens-Specific Remediation in Express — concrete code fixes
Remediation focuses on preventing unnecessary exposure, enforcing transport security, and avoiding token leakage in logs and errors. Always prefer header-based transmission and avoid moving Bearer tokens into query parameters, URLs, or HTML attributes. Use strong referrer policies and CORS settings to limit cross-origin exposure.
Example: Safe Authorization Header Usage
const express = require('express');
const app = express();
// Middleware to validate Bearer token presence and format
function validateBearerToken(req, res, next) {
const authHeader = req.headers['authorization'];
const tokenPattern = /^Bearer \S{20,}$/;
if (!authHeader || !tokenPattern.test(authHeader)) {
return res.status(401).json({ error: 'Unauthorized: Bearer token required' });
}
// Do NOT log the full authorization header
next();
}
app.use(validateBearerToken);
app.get('/v1/resource', (req, res) => {
res.json({ data: 'protected resource' });
});
app.listen(3000, () => console.log('Server running on port 3000'));
Example: Avoiding Token Echo in Errors and Logs
const morgan = require('morgan');
const app = express();
// Custom token-safe logging format
morgan.token('auth-safe', (req) => {
const hasAuth = req.headers.authorization ? '[Auth: Bearer ****]' : '[No Auth]';
return hasAuth;
});
app.use(morgan(':method :url :auth-safe :status :response-time ms'));
// Centralized error handler that removes sensitive data
app.use((err, req, res, next) => {
const publicError = {
message: 'Internal server error',
requestId: req.id
};
console.error('Request error:', { method: req.method, url: req.url, error: err.message });
res.status(500).json(publicError);
});
Example: Enforcing HTTPS and Referrer Policy
const helmet = require('helmet');
const app = express();
// Use helmet to set security headers
app.use(helmet());
app.use(helmet.referrerPolicy({ policy: 'no-referrer' }));
// Enforce HTTPS in production by rejecting insecure origins
app.use((req, res, next) => {
if (process.env.NODE_ENV === 'production' && !req.secure) {
return res.status(400).json({ error: 'HTTPS required' });
}
next();
});
app.get('/v1/secure', (req, res) => {
res.json({ message: 'Secure endpoint' });
});
Example: CORS Configuration to Limit Exposure
const cors = require('cors');
const app = express();
const corsOptions = {
origin: 'https://trusted-client.example.com',
methods: ['GET', 'POST'],
credentials: true,
exposedHeaders: [] // Do not expose Authorization to client scripts
};
app.use(cors(corsOptions));
app.get('/v1/cors-protected', (req, res) => {
res.json({ message: 'CORS-restricted endpoint' });
});
Example: Token Rotation and Short-Lived Design
// Server-side token issuance with limited validity
app.post('/v1/auth/token', (req, res) => {
const userId = req.body.userId;
// In practice, sign a JWT or reference a server-side session
const accessToken = signAccessToken(userId); // custom signing function
const expiresIn = 900; // 15 minutes
res.json({ access_token: accessToken, expires_in: expiresIn });
});
// Client must use the short-lived token and refresh when needed