Missing Tls with Jwt Tokens
How Missing TLS Manifests in JWT Tokens
Missing TLS in JWT token implementations creates several critical attack vectors that directly compromise authentication and authorization systems. The most common manifestation occurs when JWTs are transmitted over HTTP instead of HTTPS, allowing attackers to intercept tokens using network-level attacks like ARP spoofing or man-in-the-middle (MITM) techniques.
Consider a typical JWT authentication flow:
// Vulnerable: JWT transmitted over HTTP
const login = async (username, password) => {
const response = await fetch('http://api.example.com/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const { token } = await response.json();
localStorage.setItem('jwt', token); // Token stored insecurely
return token;
};When this token is sent over HTTP, an attacker on the same network can capture it using tools like Wireshark or tcpdump. The captured JWT can then be used for session hijacking, granting the attacker full access to the victim's authenticated session.
Another JWT-specific manifestation occurs during token refresh operations. Many applications implement refresh token endpoints that accept HTTP requests:
// Vulnerable refresh endpoint
app.post('/refresh', (req, res) => {
const refreshToken = req.body.refreshToken;
// No validation of transport security
const user = validateRefreshToken(refreshToken);
if (user) {
const newToken = generateJWT(user);
res.json({ token: newToken });
}
});Even if the initial authentication uses HTTPS, a refresh endpoint accessible over HTTP creates a backdoor for token theft. Attackers can continuously refresh stolen tokens without ever needing the original credentials.
JWTs are also vulnerable during storage and transmission between microservices. When services communicate over unencrypted channels, tokens can be intercepted mid-flight:
// Vulnerable: service-to-service communication over HTTP
func validateToken(tokenString string) (*User, error) {
// Token transmitted over HTTP to auth service
resp, err := http.Post("http://auth-service/validate",
"application/json", bytes.NewBuffer([]byte(`{"token":""+tokenString+""}`)))
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Process response...
}This pattern is particularly dangerous in microservices architectures where multiple services handle JWTs across internal networks that may not be properly segmented or secured.
JWT-Specific Detection
Detecting missing TLS in JWT implementations requires both automated scanning and manual code review. middleBrick's black-box scanning approach identifies TLS vulnerabilities by attempting to access JWT endpoints over HTTP and analyzing the responses.
middleBrick specifically tests JWT endpoints by:
- Identifying JWT-related endpoints through OpenAPI spec analysis or runtime discovery
- Attempting HTTP connections to authentication, token refresh, and validation endpoints
- Checking for successful responses when TLS is absent
- Analyzing JWT payloads for sensitive information exposure
- Testing for token reuse across different transport security contexts
Manual detection should focus on these JWT-specific patterns:
# Check for HTTP JWT endpoints
curl -I http://api.example.com/auth/login
curl -I http://api.example.com/refresh
curl -I http://api.example.com/validate
# Test for token transmission over insecure channels
# Look for Authorization headers in HTTP responses
curl -v http://api.example.com/protected 2>&1 | grep -i authorization
# Check for mixed content in JWT implementations
# Search code for HTTP URLs in authentication contexts
grep -r "http://" --include="*.js" --include="*.ts" --include="*.py" --include="*.go" auth/
grep -r "http://" --include="*.java" --include="*.kt" controllers/
Code review should examine JWT libraries and their configuration:
# Vulnerable: jsonwebtoken without secure defaults
import jwt
def create_token(payload):
return jwt.encode(payload, 'secret-key', algorithm='HS256')
# Missing: algorithm validation, secure key storage
# Vulnerable: no TLS enforcement in HTTP clients
def call_api(token):
headers = {'Authorization': f'Bearer {token}'}
response = requests.get('http://api.example.com/data', headers=headers)
# Missing: verify=True, HTTPS enforcement
middleBrick's scanning also identifies JWT-specific issues like:
- Tokens transmitted in query parameters over HTTP
- Missing Secure/HttpOnly flags on JWT cookies
- Weak JWT signing algorithms exposed over insecure channels
- Token replay attacks across different transport security contexts
JWT-Specific Remediation
Remediating missing TLS in JWT implementations requires both transport security enforcement and JWT-specific security hardening. The foundation is implementing HTTPS everywhere JWTs are transmitted.
Node.js/Express JWT implementation with TLS enforcement:
// Secure JWT implementation with TLS enforcement
const express = require('express');
const jwt = require('jsonwebtoken');
const helmet = require('helmet');
const app = express();
// Enforce HTTPS using helmet
app.use(helmet());
app.use(helmet.hsts({
maxAge: 31536000,
includeSubDomains: true,
preload: true
}));
// JWT authentication middleware
const authenticateJWT = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ message: 'Missing token' });
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'] // Algorithm whitelisting
});
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ message: 'Invalid token' });
}
};
// Secure login endpoint (HTTPS only)
app.post('/auth/login', express.json(), (req, res) => {
const { username, password } = req.body;
// Authenticate user
const user = authenticateUser(username, password);
if (!user) {
return res.status(401).json({ message: 'Invalid credentials' });
}
// Create JWT with secure options
const token = jwt.sign(
{ userId: user.id, username: user.username },
process.env.JWT_SECRET,
{
expiresIn: '1h',
issuer: 'your-domain.com',
audience: 'your-app'
}
);
// Set secure cookie for JWT
res.cookie('jwt', token, {
httpOnly: true,
secure: true, // Only over HTTPS
sameSite: 'strict',
maxAge: 3600000
});
res.json({ token, user: { id: user.id, username: user.username } });
});
// Refresh endpoint with TLS enforcement
app.post('/auth/refresh', express.json(), (req, res) => {
// Verify TLS connection
if (!req.secure) {
return res.status(400).json({
message: 'Refresh endpoint requires HTTPS'
});
}
const { refreshToken } = req.body;
try {
const decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET, {
algorithms: ['HS256']
});
// Create new access token
const newToken = jwt.sign(
{ userId: decoded.userId },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
res.json({ token: newToken });
} catch (err) {
res.status(401).json({ message: 'Invalid refresh token' });
}
});
app.listen(443, () => {
console.log('Server running on HTTPS port 443');
});Python/FastAPI implementation with comprehensive TLS and JWT security:
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
from jose import JWTError, jwt
from datetime import datetime, timedelta
import os
app = FastAPI()
# Enforce HTTPS redirect
app.add_middleware(HTTPSRedirectMiddleware)
# JWT security scheme
security = HTTPBearer()
def get_current_user(token: str = Depends(security)):
if not token:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="No token provided"
)
try:
payload = jwt.decode(
token.credentials,
os.getenv('JWT_SECRET'),
algorithms=['HS256']
)
return payload
except JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or expired token"
)
@app.post("/auth/login")
async def login(credentials: dict):
# Authenticate user (implementation omitted)
user = authenticate_user(credentials.get("username"), credentials.get("password"))
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials"
)
# Create JWT with secure configuration
access_token_expires = timedelta(hours=1)
access_token = jwt.encode(
{
"sub": user.id,
"username": user.username,
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + access_token_expires,
"iss": "your-domain.com",
"aud": "your-app"
},
os.getenv('JWT_SECRET'),
algorithm="HS256"
)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/protected")
async def protected_route(current_user: dict = Depends(get_current_user)):
return {"message": "Access granted", "user": current_user}
@app.post("/auth/refresh")
async def refresh_token(refresh_payload: dict):
# Verify TLS connection
if not app.state.is_https:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Refresh endpoint requires HTTPS"
)
# Refresh token logic with HTTPS enforcement
try:
payload = jwt.decode(
refresh_payload.get("refresh_token"),
os.getenv('REFRESH_SECRET'),
algorithms=['HS256']
)
# Create new access token
new_token = jwt.encode(
{
"sub": payload["sub"],
"username": payload["username"],
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(hours=1),
"iss": "your-domain.com"
},
os.getenv('JWT_SECRET'),
algorithm="HS256"
)
return {"access_token": new_token}
except JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid refresh token"
)Related CWEs: encryption
| CWE ID | Name | Severity |
|---|---|---|
| CWE-319 | Cleartext Transmission of Sensitive Information | HIGH |
| CWE-295 | Improper Certificate Validation | HIGH |
| CWE-326 | Inadequate Encryption Strength | HIGH |
| CWE-327 | Use of a Broken or Risky Cryptographic Algorithm | HIGH |
| CWE-328 | Use of Weak Hash | HIGH |
| CWE-330 | Use of Insufficiently Random Values | HIGH |
| CWE-338 | Use of Cryptographically Weak PRNG | MEDIUM |
| CWE-693 | Protection Mechanism Failure | MEDIUM |
| CWE-757 | Selection of Less-Secure Algorithm During Negotiation | HIGH |
| CWE-261 | Weak Encoding for Password | HIGH |