Jwt Misconfiguration with Api Keys
How JWT Misconfiguration Manifests in API Keys
When an API treats the same value used as an API key also as the secret for signing or verifying JSON Web Tokens, the two mechanisms become coupled. An attacker who can obtain or guess the API key can then forge valid JWTs, bypassing any intended token‑based authentication or authorization.
This coupling often appears in code that:
- Extracts the API key from a request header (e.g.,
x-api-key) and directly passes it tojwt.verify()orjwt.sign()as the secret. - Fails to enforce the signing algorithm, allowing a token with the
nonealgorithm to be accepted when the secret is known. - Uses a low‑entropy API key (e.g., a simple string like
test123) as the JWT secret, making brute‑force feasible.
The vulnerable flow typically looks like this:
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
// Insecure: API key reused as JWT secret
app.use((req, res, next) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey) return res.status(401).send('Missing API key');
req.apiKey = apiKey; // later used as JWT secret
next();
});
app.get('/data', (req, res) => {
const token = req.headers.authorization?.split(' ')[1];
try {
// No algorithm enforcement; secret is the API key
const payload = jwt.verify(token, req.apiKey);
res.json({ message: 'Data', user: payload });
} catch (err) {
res.status(401).send('Invalid token');
}
});
app.listen(3000);
If an attacker learns the API key (e.g., from logs, client‑side code, or a misconfigured public repo), they can create a token such as:
const jwt = require('jsonwebtoken');
const apiKey = 'leaked‑api‑key'; // obtained elsewhere
const forged = jwt.sign({ sub: 'admin', role: 'admin' }, apiKey, { algorithm: 'HS256' });
console.log(forged); // send this as Authorization header
The resulting token will be accepted by the vulnerable endpoint, granting the attacker privileged access.
API Keys‑Specific Detection
middleBrick’s unauthenticated black‑box scan includes checks that specifically target the coupling of API keys and JWT secrets. When you submit a URL, the scanner:
- Identifies endpoints that require an
x-api-key(or similar) header. - Attempts to replay a request with a valid API key to confirm the header is checked.
- For each identified endpoint, tries to forge a JWT using the discovered API key as the HMAC secret.
- Tests algorithm confusion by sending a token with the
nonealgorithm and the API key as the secret. - Reports a finding when a forged token is accepted, indicating JWT misconfiguration tied to the API key.
You can run this check locally with the middleBrick CLI:
# Install the CLI (npm)
npm i -g middlebrick
# Scan an API endpoint
middlebrick scan https://api.example.com
The output will include a finding similar to:
Finding: JWT secret derived from API key
Severity: High
Category: Broken Authentication
Description: The API accepts JWTs signed with the x‑api‑key value as the HMAC secret, allowing token forgery.
Remediation: Use a separate, high‑entropy secret for JWT signing and verify the algorithm explicitly.
In the Dashboard, the finding appears under the "Authentication" category with a severity badge and a link to the exact request/response that demonstrated the issue.
API Keys‑Specific Remediation
The fix is to decouple API key authentication from JWT verification. Use the API key solely for identifying the caller, and rely on an independent, strong secret (or asymmetric key pair) for JWTs. Additionally, always enforce the signing algorithm.
Here is a corrected Node.js/Express example:
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
// Independent secrets – load from environment, never hard‑code
const API_KEY = process.env.API_KEY; // random, high‑entropy value
const JWT_SECRET = process.env.JWT_SECRET; // separate secret for HS256
// For asymmetric keys you could use a private/public PEM pair
// Middleware: verify API key only
function verifyApiKey(req, res, next) {
const key = req.headers['x-api-key'];
if (key === API_KEY) {
return next();
}
return res.status(401).send('Invalid API key');
}
// Middleware: verify JWT with explicit algorithm
function verifyJwt(req, res, next) {
const auth = req.headers.authorization;
if (!auth) return res.status(401).send('Missing token');
const token = auth.split(' ')[1];
try {
const payload = jwt.verify(token, JWT_SECRET, { algorithms: ['HS256'] });
req.user = payload;
next();
} catch (err) {
return res.status(401).send('Invalid token');
}
}
app.get('/data', verifyApiKey, verifyJwt, (req, res) => {
res.json({ message: 'Secure data', user: req.user });
});
app.listen(3000);
Key points in the fix:
API_KEYandJWT_SECRETare distinct values stored securely (e.g., in a vault or environment variables).- The JWT verification explicitly limits acceptable algorithms to
HS256(orRS256for asymmetric keys). - If you prefer asymmetric cryptography, replace the HMAC secret with a private key for signing and a public key for verification:
const privateKey = fs.readFileSync('private.pem'); const publicKey = fs.readFileSync('public.pem'); // signing jwt.sign(payload, privateKey, { algorithm: 'RS256' }); // verification jwt.verify(token, publicKey, { algorithms: ['RS256'] }); - Rotate both the API key and JWT secret periodically, and ensure they are never logged or exposed in client‑side bundles.
After applying these changes, rescan the endpoint with middleBrick. The scanner will no longer accept a forged token, and the finding will disappear from the report.
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |