Phishing Api Keys in Express with Bearer Tokens
Phishing API Keys in Express with Bearer Tokens — how this specific combination creates or exposes the vulnerability
In Express applications, using Bearer tokens for authorization means an API key or session token is typically sent in the Authorization header as Authorization: Bearer <token>. If the application or its dependencies expose that token in logs, error messages, client‑side code, or insecure redirects, an attacker can phish the token via social engineering, fake dashboards, or compromised documentation. A common pattern that increases risk is concatenating static prefixes with values that may be logged or echoed, for example constructing headers from user input or configuration values that are inadvertently surfaced in the client.
Another exposure vector is when the Express app serves API documentation (such as an OpenAPI spec) or debug endpoints that include example Bearer tokens. If those examples are real keys or if the spec references environment variables that are printed in error pages, an attacker can harvest valid tokens. Because Bearer tokens are often long‑lived compared to short‑lived session cookies, a phished token can grant extended access to protected endpoints. This is especially dangerous when the token has broad scopes (e.g., read/write across services) and lacks tight scope or time restrictions.
Middleware that logs request headers indiscriminately can also create a phishing surface: if a log line containing Authorization: Bearer abc123 is stored or forwarded to a monitoring system that is not tightly controlled, an attacker who gains access to those logs can obtain valid credentials. Similarly, client‑side code that embeds tokens in JavaScript or HTML (for example to call a backend route) can be extracted through XSS or simply viewed source, enabling phishing campaigns that trick users into revealing those embedded tokens.
Supply‑chain and dependency risks compound this: if an Express app uses packages that inadvertently transmit tokens to external endpoints or print them during error handling, an attacker does not need to exploit the app itself to phish the token. The combination of Express routing, Bearer token usage, and any weak logging, documentation, or error handling provides multiple opportunities for token exposure. Attackers then use these exposed tokens to perform authenticated requests, bypassing authentication controls and potentially escalating impact across integrated services.
When scanning such an API with a black‑box approach, checks for Authentication, Data Exposure, and Unsafe Consumption can surface indicators like tokens in error responses, overly verbose stack traces, or reflected header values. Because middleBrick tests the unauthenticated attack surface, it can identify endpoints that leak tokens in responses or documentation without requiring credentials, helping teams discover phishing surfaces before attackers do.
Bearer Tokens-Specific Remediation in Express — concrete code fixes
Remediation focuses on preventing token leakage in logs, error messages, and client‑facing code, and on ensuring tokens are treated as secrets that never appear in URLs or source that can be inspected by browsers or third‑party tools.
1. Avoid logging Authorization headers
Ensure your logging middleware redacts or removes the Authorization header. Example with a redaction helper:
function sanitizeLogHeaders(headers) {
const { authorization, ...safe } = headers;
return { ...safe, authorization: authorization ? '[REDACTED]' : undefined };
}
app.use((req, res, next) => {
console.info('Request', {
method: req.method,
url: req.url,
headers: sanitizeLogHeaders(req.headers),
});
next();
});
2. Send tokens only via Authorization header, never in URLs or body
Clients must use the Authorization header; servers must reject tokens in query parameters or form bodies. Add a validation middleware:
app.use((req, res, next) => {
const viaQuery = req.query.access_token || req.query.bearer;
if (viaQuery) {
return res.status(400).json({ error: 'invalid_request', error_description: 'Token must not appear in URL' });
}
next();
});
3. Use secure, HttpOnly cookies for session tokens when feasible
For web sessions, prefer cookies with Secure and SameSite attributes instead of manually managing Bearer tokens in JavaScript. If you must use Bearer tokens in clients (e.g., SPAs), keep them in memory and avoid persistent storage.
4. Centralize token handling and avoid echoing tokens in responses
Do not return token values in responses or error details. Example of safe error handling:
app.use((err, req, res, next) => {
res.status(err.status || 500).json({
error: err.expose ? err.message : 'internal_server_error',
// Never include req.headers.authorization in the response
});
});
5. Protect API documentation and remove real tokens from examples
Ensure generated OpenAPI specs do not contain real keys. Use placeholders and enforce a pre-commit check that rejects literal tokens in spec files:
// BAD: real token in spec
const spec = {
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiMTIzIn0', // remove
},
},
},
};
// GOOD: placeholder
const spec = {
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
example: 'your-valid-jwt-here',
},
},
},
};
6. Enforce short lifetimes and strict scopes for tokens
Where possible, use short‑lived access tokens and rotate refresh tokens. Validate scopes on each request to ensure a token issued for read‑only endpoints is not used to perform write operations.
7. Use environment variables and secret management
Keep signing keys and shared secrets out of source code. Load them securely at runtime:
require('dotenv').config();
const jwt = require('jsonwebtoken');
const secret = process.env.JWT_SECRET;
if (!secret) throw new Error('JWT_SECRET is required');
const token = jwt.sign({ sub: userId }, secret, { expiresIn: '15m' });