document.body.classList.toggle('menu-open', show); // Add 'menu-open' class to body.

JWT Security Part 7: Advanced JWT Security measures

Jan Masters
Written by Jan Masters
December 26, 2024
Tags – ,
Welcome back and strap in, because today’s blog is all about taking our JWT security game to the next level. It’s time for some advanced JWT Security measures!

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?

JWT fingerprinting is like giving each token a unique pair of socks, but now we’re adding DNA testing to those socks! It’s about making sure that when someone presents a token, we can verify it’s being used in the same environment it was issued for.

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

Nested JWTs, or JSON Web Encryption (JWE), are like Russian nesting dolls, but for security. You encrypt the JWT, then sign the encrypted 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
```
				
			
This approach provides both confidentiality and integrity. It’s like putting your secret message in a locked box, then having that box certified by a notary.

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)
```
				
			
This makes token theft much harder, as the token is essentially useless outside of the specific TLS session it was created for.

Dynamic secrets: keep them guessing

Instead of using a static secret key, use a dynamic one that changes based on certain parameters. It’s like having a different secret handshake for each day of the week.
				
					```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()));
```
				
			
This approach means that even if one token’s secret is somehow compromised, it doesn’t compromise the entire system.

Claim-based authorisation: fine-grained access control

Instead of just checking if a token is valid, use the claims within the token to make fine-grained authorisation decisions. It’s like checking not just if someone has a backstage pass, but what specific areas they’re allowed into.
				
					```
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)
```
				
			
This allows for much more nuanced control over what actions a user can perform, based on the specific claims in their token.

Continuous token validation: trust but verify (constantly!)

Don’t just check the token once and call it a day. Implement a system of continuous validation, especially for long-lived sessions or high-security operations.
				
					```
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()
	)
```
				
			
This is like having a security guard that doesn’t just check your ID at the door, but periodically walks around making sure everyone still belongs there.

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)
```
				
			
It’s like putting a bouncer at your API’s door who only lets in a certain number of people per hour, no matter how much they insist they’re VIPs.

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.

Like what you see? Share with a friend!

Jan Masters

This article is written by

Jan Masters

Cyber Security Engineer

In cyber security, there are no magic spells – just knowledge, experience, luck, and a touch of wizardry to turn challenges into solutions.

Cyber Security Engineer Jan – our resident Cyber Wizard (@FlyingPhishy on X, if you’re asking) – is a jack-of-all-trades consultant specialising in infrastructure, cloud, and R&D. He delivers high-quality, holistic penetration tests and drives innovation from within, ensuring our penetration testing services are modern and break the mould.