Business information leak - JWT
Need
Exclusion of sensitive data from JSON Web Token payloads
Context
• Usage of Rust 1.75+ for building application services
• Usage of the actix-web crate for handling HTTP requests
• Usage of the jsonwebtoken crate for issuing JSON Web Tokens
Description
1. Non compliant code
use actix_web::{post, web, HttpResponse};
use jsonwebtoken::{encode, get_current_timestamp, EncodingKey, Header};
use serde::Serialize;
#[derive(Clone)]
struct User {
id: String,
email: String,...The `issue_token` handler below copies the email, the password hash and the national identification number of the authenticated user into the claims of a JSON Web Token. A signed JWT is not encrypted. Its payload is only Base64url-encoded, so anyone who holds the token can read every claim by decoding the middle segment, without knowing the key. Tokens travel in `Authorization` headers and cookies, are kept in browser storage and are often written to logs by proxies and monitoring tools, which spreads that personal data far beyond the database. The password hash is especially dangerous: an attacker who collects tokens can run an offline guessing attack against it without ever touching the application.
2. Steps
• Treat every JWT claim as public, because signed tokens are only Base64url-encoded.
• Never put passwords, password hashes, API keys, payment data or personal identification numbers in the claims passed to `jsonwebtoken::encode`.
• Limit the payload to an opaque `sub` identifier, the timestamps and the minimum authorization data, such as roles or scopes.
• Retrieve personal attributes from the database or an authorized API when a service needs them.
• Use encrypted tokens (JWE) with a library that supports them if confidential data must travel in the token.
3. Secure code example
use actix_web::{post, web, HttpResponse};
use jsonwebtoken::{encode, get_current_timestamp, EncodingKey, Header};
use serde::Serialize;
#[derive(Clone)]
struct User {
id: String,
}...The corrected handler keeps only the claims that the receiving services need to authorize a request: `sub`, an opaque user identifier, and the `iat` and `exp` timestamps. Personal and secret data stays in the database. When a service needs the email or other attributes, it looks them up by `sub` through an authorized call, which also guarantees that it always sees the current value instead of a copy frozen in the token. If a token must carry confidential data, it has to be encrypted as a JWE rather than only signed. The `jsonwebtoken` crate does not implement JWE, so the safest design is to keep that data out of the token.
References
• 213. Business information leak - JWT