← Back to Learn

Advanced JWT Topics

Advanced concepts and techniques for expert-level JWT implementations.

1. JWE (JSON Web Encryption)

JWE allows you to encrypt JWT payloads, not just sign them. Use when you need to protect sensitive data.

JWE Example
// JWE encrypts the payload
// Structure: header.encrypted_key.iv.ciphertext.tag

const jose = require('jose');

// Encrypt JWT
const secret = new TextEncoder().encode('your-256-bit-secret');
const jwt = await new jose.EncryptJWT({ sub: 'user123' })
  .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' })
  .setIssuedAt()
  .setExpirationTime('2h')
  .encrypt(secret);

// Decrypt JWT
const { payload } = await jose.jwtDecrypt(jwt, secret);

// Use cases:
// - Sensitive data in payload
// - Compliance requirements (HIPAA, PCI-DSS)
// - Multi-party scenarios

2. Nested JWTs

A JWT that contains another JWT as a claim. Useful for delegation and multi-party scenarios.

Nested JWT
// Create inner JWT
const innerToken = jwt.sign(
  { sub: 'user123', permissions: ['read'] },
  innerSecret,
  { algorithm: 'HS256' }
);

// Create outer JWT containing inner JWT
const outerToken = jwt.sign(
  {
    sub: 'service-account',
    delegatedToken: innerToken,  // Nested JWT
    issuer: 'auth-service'
  },
  outerSecret,
  { algorithm: 'RS256' }
);

// Verify outer token
const outerDecoded = jwt.verify(outerToken, outerPublicKey);

// Extract and verify inner token
const innerDecoded = jwt.verify(
  outerDecoded.delegatedToken,
  innerSecret
);

// Use cases:
// - Token delegation
// - Multi-party authentication
// - Service-to-service with user context

3. Token Chaining

Chain multiple tokens together for complex authorization scenarios.

Token Chaining
// User token
const userToken = jwt.sign(
  { sub: 'user123', role: 'user' },
  userSecret
);

// Service token (references user token)
const serviceToken = jwt.sign(
  {
    sub: 'service-id',
    userToken: userToken,  // Chained token
    serviceRole: 'api-service'
  },
  serviceSecret
);

// Verify chain
function verifyTokenChain(token) {
  const decoded = jwt.verify(token, serviceSecret);
  
  // Verify chained user token
  const userDecoded = jwt.verify(
    decoded.userToken,
    userSecret
  );
  
  return {
    service: decoded,
    user: userDecoded
  };
}

4. JWKS (JSON Web Key Set)

Standard way to publish public keys for token verification.

JWKS Implementation
// JWKS endpoint
app.get('/.well-known/jwks.json', (req, res) => {
  res.json({
    keys: [
      {
        kty: 'RSA',
        kid: 'key-1',
        use: 'sig',
        alg: 'RS256',
        n: 'public-key-modulus',
        e: 'AQAB'
      }
    ]
  });
});

// Client fetches JWKS and verifies token
const jwksClient = require('jwks-rsa');
const client = jwksClient({
  jwksUri: 'https://auth.example.com/.well-known/jwks.json'
});

function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    const signingKey = key.publicKey || key.rsaPublicKey;
    callback(null, signingKey);
  });
}

jwt.verify(token, getKey, {
  algorithms: ['RS256']
}, (err, decoded) => {
  // Token verified with public key from JWKS
});

5. Token Introspection

RFC 7662 defines a standard way to query token metadata from the authorization server.

Token Introspection
// Introspection endpoint
app.post('/oauth/introspect', async (req, res) => {
  const { token } = req.body;
  
  try {
    const decoded = jwt.verify(token, SECRET);
    
    // Check if token is active
    const isActive = !isTokenBlacklisted(token) && 
                     !isTokenExpired(token);
    
    res.json({
      active: isActive,
      sub: decoded.sub,
      exp: decoded.exp,
      iat: decoded.iat,
      scope: decoded.scope,
      client_id: decoded.client_id
    });
  } catch (error) {
    res.json({ active: false });
  }
});

// Client uses introspection
const response = await fetch('/oauth/introspect', {
  method: 'POST',
  body: JSON.stringify({ token }),
  headers: { 'Content-Type': 'application/json' }
});

const introspection = await response.json();
if (introspection.active) {
  // Token is valid and active
}

6. Proof of Possession (PoP) Tokens

Tokens bound to a specific client or key to prevent token theft and replay attacks.

PoP Token
// Generate client key pair
const { publicKey, privateKey } = crypto.generateKeyPairSync('ec', {
  namedCurve: 'prime256v1'
});

// Include public key in token
const token = jwt.sign(
  {
    sub: 'user123',
    cnf: {
      jwk: publicKey  // Client's public key
    }
  },
  serverSecret
);

// Client must prove possession of private key
function provePossession(token, privateKey) {
  const decoded = jwt.verify(token, serverSecret);
  const clientPublicKey = decoded.cnf.jwk;
  
  // Client signs a nonce with private key
  const nonce = crypto.randomBytes(32);
  const signature = crypto.sign(null, nonce, privateKey);
  
  // Server verifies signature with public key from token
  const isValid = crypto.verify(
    null,
    nonce,
    clientPublicKey,
    signature
  );
  
  return isValid;
}

7. Token Aggregation

Combine multiple tokens into a single aggregated token for simplified authorization.

Token Aggregation
// Multiple service tokens
const serviceAToken = jwt.sign({ service: 'A', permissions: ['read'] }, secretA);
const serviceBToken = jwt.sign({ service: 'B', permissions: ['write'] }, secretB);

// Aggregate into single token
const aggregatedToken = jwt.sign(
  {
    sub: 'user123',
    tokens: {
      serviceA: serviceAToken,
      serviceB: serviceBToken
    },
    aggregatedAt: Math.floor(Date.now() / 1000)
  },
  aggregationSecret
);

// Verify aggregated token and extract service tokens
function verifyAggregated(token) {
  const decoded = jwt.verify(token, aggregationSecret);
  
  return {
    user: decoded.sub,
    serviceA: jwt.verify(decoded.tokens.serviceA, secretA),
    serviceB: jwt.verify(decoded.tokens.serviceB, secretB)
  };
}

8. Stateless vs Stateful Considerations

When to Use Stateless (JWT)

  • Distributed systems and microservices
  • High scalability requirements
  • No need for immediate revocation
  • Stateless API design

When to Use Stateful (Sessions)

  • Need immediate token revocation
  • Complex authorization logic
  • Single-server applications
  • Need to track user sessions

💡 Hybrid Approach

Use JWT for access tokens (short-lived) and maintain session state for refresh tokens and revocation.