We’re not just locking the door; we’re installing a moat, training attack dragons, and setting up an elaborate series of Indiana Jones-style booby traps. Let’s dive into some advanced JWT security measures that will make potential attackers wish they’d chosen an easier target.
JWT fingerprinting: who goes there?
What can we fingerprint?
Modern browser fingerprinting can collect various characteristics that, when combined, create a highly unique identifier:
1. Basic browser data
- User agent and browser version
- Screen resolution and colour depth
- System language and timezone
- Available fonts and plugins
- Touch support status
- IP address (more network than browser)
2. Advanced identifiers
- Canvas fingerprinting: like asking each browser to draw a picture and checking their unique artistic style
- Audio processing: each device processes audio slightly differently
- WebGL details: graphics capabilities act like a hardware ID
- Browser feature support: which APIs and features are available
3. Behavioural elements
- Incognito Mode detection
– Battery status (when available) - Network characteristics
- Input device behaviours
Open-Source tools
Several battle-tested libraries can help implement fingerprinting:
- **FingerprintJS**
- **ClientJS**
- **ImprintJS**
- **ThumbmarkJS**
- **DetectIncognitoJS**
- **OverpoweredJS**
Fingerprint is an open source tool that combines a number of different JS and IP fingerprinting scripts to provide highly unique identifiers.
Fingerprinter repo: https://github.com/GONZOsint/fingerprinter.
Advanced JWT Security smart implementation tips
1. Layer your approach
- Don’t rely on a single fingerprinting method
- Combine multiple techniques for better accuracy
- Weight different factors based on their reliability
2. Handle changes gracefully
- Browser updates can change fingerprints
- Allow for some variation in verification
- Consider implementing a similarity score rather than exact matching
3. Performance considerations
- Fingerprinting can be resource-intensive
- Cache results where appropriate
- Consider running intensive checks only on suspicious activity
Privacy-first fingerprinting
Remember, with great power comes great responsibility. Unfortunately these and similar powers are not widely used respectfully in the physical and “meta” world (Internet for most of us):
1. Transparency
- Be clear about what you’re collecting
- Explain why you need this information
- Provide privacy policy details
2. Data minimisation
- Only collect what you genuinely need
- Have clear data retention policies
- Provide opt-out options where possible
3. Regulatory compliance
- Follow requirements, regulations and guidelines that are applicable to your region and industry such as (but not limited to): GDPR and CCPA
- Stay updated on privacy regulations
Advanced JWT Security best practices
1. Implementation strategy
- Start with basic fingerprinting
- Add advanced methods based on security needs
- Regular review and updates of fingerprinting methods
- Monitor for false positives and user experience impact
2. Security balance
- Consider user privacy vs security needs
- Implement progressive security measures
- Have fallback mechanisms for legitimate changes
- Monitor for bypass attempts
3. Maintenance guidelines
- Regularly update fingerprinting libraries
- Monitor for new browser features/changes
- Keep track of fingerprinting effectiveness
- Adjust tolerance levels based on real-world data
When fingerprint verification fails
Have a clear plan for handling mismatches:
1. Legitimate changes
- Browser updates
- Device changes
- Operating system updates
2. Suspicious activities
- Multiple rapid fingerprint changes
- Impossible geographic jumps
- Different and potentially flagged IP addresses
- Known malicious patterns
3. Response actions
- Step-up authentication
- Temporary lockouts
- User notification
- Security team alerts
Remember: Fingerprinting is just one layer in your advanced JWT security onion. It should complement, not replace, other security measures like proper token validation, secure storage, and regular security audits.
Nested JWTs: a token within a token
```
function createNestedJWT(payload, encryptionKey, signingKey):
// Step 1: Encrypt the payload
encryptedToken = encrypt(payload, encryptionKey)
// Step 2: Sign the encrypted token
signedToken = sign(encryptedToken, signingKey)
return signedToken
```
Token binding: chaining your JWT to TLS
Token binding ties your JWT to the TLS session it was issued in. It’s like handcuffing your token to the user’s browser.
```javascript
function createTokenBinding(token, tlsUnique):
// Combine token with TLS session identifier
hash = hashFunction(token + tlsUnique)
return encodeToBase64(hash)
```
Dynamic secrets: keep them guessing
```javascript
const getSecretKey = (userId, timestamp) => {
const baseSecret = process.env.JWT_BASE_SECRET;
const dynamicPart = crypto.createHmac('sha256', baseSecret)
.update(`${userId}-${Math.floor(timestamp / (24 * 60 * 60 * 1000))}`)
.digest('hex');
return baseSecret + dynamicPart;
};
const token = jwt.sign({ userId }, getSecretKey(userId, Date.now()));
```
Claim-based authorisation: fine-grained access control
```
function authoriseAction(token, requiredPermissions):
// Verify and decode the token
decodedToken = verifyToken(token, secretKey)
// Extract permissions from the token
userPermissions = extractClaims(decodedToken, "permissions")
// Check if all required permissions are satisfied
return all(requiredPermissions in userPermissions)
```
Continuous token validation: trust but verify (constantly!)
```
function continuousValidation(token, interval):
scheduleTask(interval, function():
try:
// Re-verify the token
decodedToken = verifyToken(token, secretKey)
// Perform additional validation (e.g., check revocation)
if isRevoked(decodedToken):
handleInvalidToken()
catch (error):
handleInvalidToken()
)
```
Throttling and rate limiting: slow down, speed racer!
Implement rate limiting on your token issuance and verification endpoints. This helps prevent brute force attacks and reduces the impact of token theft.
```
function applyRateLimit(apiRoute, maxRequests, timeWindow):
// Track requests per IP within the time window
trackRequests(apiRoute, maxRequests, timeWindow)
```
Advanced JWT Security: in conclusion
Implementing these advanced JWT security measures turns your JWT deployment from a simple locked door into a good looking fortress that is yet to be battle tested by a paid professional or a threat actor. It all depends who gets there first!
Remember, with great security comes great complexity. Each of these measures adds another layer of protection or integrity, but also another potential point of failure in terms of functionality, security, performance and user experience.
The key is to assess your specific security needs and implement the measures that make sense for your application. Maybe you need the full Fort Knox treatment with advanced JWT security measures, or maybe a well-implemented basic JWT setup with a couple of these advanced features is sufficient.
The goal isn’t to create an impenetrable system (which is impossible), but to make unauthorised access so difficult and time-consuming that attackers give up and attack an easier target, or they simply decide to attack the human chain.
Stay tuned for our final post in this series, where we’ll look at a real-world case study of JWT implementation by Microsoft in their cloud offering – Microsoft Cloud – which contains various ecosystems for Azure and Entra, Microsoft 365, Power Platform and the rest.