Api Key Exposure in Sails with Jwt Tokens
Api Key Exposure in Sails with Jwt Tokens — how this specific combination creates or exposes the vulnerability
When JWT tokens are used for authentication in a Sails application, improper handling can inadvertently expose static API keys that back-end services rely on. In Sails, services and models often read keys from environment variables or configuration files, and those keys may be embedded in JWT payloads or logged as part of token generation or error reporting. If a JWT is transmitted without HTTPS, deserialized on the client side, or constructed with sensitive claims, an API key can be leaked through token inspection, logs, or insecure storage.
For example, a Sails hook that attaches an API key to a JWT claim (such as scope or client identifier) increases exposure surface: the token may be stored in browser local storage, copied into logs, or cached by intermediaries. Because JWTs are bearer tokens, anyone who obtains the token can present it alongside the exposed API key to downstream services. This becomes critical if the JWT is used to authorize calls from the client to back-end endpoints that expect an API key in headers, effectively bridging client-side token usage with server-side key-based auth in ways that bypass intended isolation.
During a middleBrick scan, unauthenticated checks can detect endpoints that issue JWTs containing key-like strings, reflect API keys in error messages, or accept tokens with overprivileged scopes. Findings may reference patterns such as secret leakage in JWT claims (e.g., api_key or secret fields), missing transport protections, or insecure token storage on the client. These issues map to OWASP API Top 10 A07:2021 — Identification and Authentication Failures, and can align with PCI-DSS and SOC2 controls around secret management. middleBrick’s LLM/AI Security checks additionally probe for prompt or configuration leakage if JWTs are echoed in model inputs or logs, highlighting risks where tokens inadvertently carry sensitive metadata.
In practice, a Sails app might generate a JWT like this, embedding an API key in a non-standard claim:
const jwt = require('jsonwebtoken');
const apiKey = process.env.EXTERNAL_API_KEY;
const payload = {
sub: 'user-123',
scope: 'api access',
api_key: apiKey
};
const token = jwt.sign(payload, process.env.JWT_SECRET, { algorithm: 'HS256' });
res.json({ token });
If EXTERNAL_API_KEY is a long-lived key used to call third-party services, embedding it in the JWT means any party with the token can derive the key. Logging the token or including it in URLs compounds exposure. MiddleBrick’s scan would flag such patterns under Data Exposure and Unsafe Consumption, noting that secrets should not travel in tokens and that tokens must be protected via HTTPS and short lifetimes.
Additionally, Sails policies that automatically attach user data to JWTs can inadvertently include sensitive metadata. Without strict validation, debug modes or verbose errors might echo configuration values that include key material. middleBrick tests for these conditions by probing endpoints and inspecting token contents, ensuring findings include remediation guidance such as removing sensitive fields from claims, enforcing HTTPS, and isolating key usage to server-side contexts only.
Jwt Tokens-Specific Remediation in Sails — concrete code fixes
Remediation centers on ensuring JWTs never carry API keys, using HTTPS consistently, and minimizing token scope and lifetime. In Sails, keep sensitive keys strictly in server-side environment variables and reference them only in server-to-server calls, never embedding them in tokens issued to clients.
Instead of embedding an API key, use JWTs to carry only non-sensitive identity and permission claims. Authorize requests to external services on the server using keys stored securely in Sails services, not passed through tokens. Here is a safer pattern that issues a token without key material:
const jwt = require('jsonwebtoken');
const payload = {
sub: 'user-123',
scope: 'api access',
iss: 'sails-app',
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (15 * 60) // 15-minute expiry
};
const token = jwt.sign(payload, process.env.JWT_SECRET, { algorithm: 'HS256' });
return token;
On the server side, create a Sails service (e.g., services/externalApi.js) that holds the API key and makes authorized calls using that key, keeping it out of the token and out of client code:
// services/externalApi.js
const axios = require('axios');
module.exports = {
async callExternal(data) {
const response = await axios.post('https://thirdparty.example.com/endpoint', data, {
headers: { 'x-api-key': process.env.EXTERNAL_API_KEY }
});
return response.data;
}
};
In controllers, decode the JWT to verify identity and scope, then invoke the server-side service with the API key stored securely in the environment:
module.exports.someAction = async function (req, res) {
try {
const decoded = jwt.verify(req.headers.authorization.split(' ')[1], process.env.JWT_SECRET);
if (!decoded.scope || !decoded.scope.includes('api')) {
return res.unauthorized('Insufficient scope');
}
const result = await ExternalApi.callExternal({ userId: decoded.sub, payload: req.body });
return res.ok(result);
} catch (err) {
return res.badRequest('Invalid token');
}
};
Always enforce HTTPS in Sails by setting trust: true behind a proxy and configuring hsts in config/http.js. Avoid storing tokens in local storage; prefer httpOnly cookies with Secure and SameSite attributes to mitigate theft. Rotate JWT_SECRET and EXTERNAL_API_KEY regularly and audit logs to ensure tokens and keys are not echoed in error traces. middleBrick’s dashboard and CLI can validate these fixes by re-scanning the endpoint and confirming that tokens no longer contain sensitive claims and that API keys remain server-side only.