HIGH unicode normalizationadonisjsjwt tokens

Unicode Normalization in Adonisjs with Jwt Tokens

Unicode Normalization in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability

Unicode normalization issues arise when equivalent characters with different byte representations are treated as distinct. In AdonisJS, if user-controlled identifiers (e.g., usernames or scopes) are used to build JWT tokens without normalization, tokens that should be equivalent may be represented differently. This can lead to authentication bypass or token confusion if your verification logic compares strings without normalizing them first.

AdonisJS applications often use JWT tokens for stateless authentication. If you accept a payload field such as sub, username, or a custom claim that includes Unicode characters, and then sign the token, a client may supply a visually identical string that normalizes to a different canonical form. For example, the character é can be represented as a single code point U+00E9 or as two code points (e ´ combining accent). Without normalization, these two forms may produce different signatures, yet your application might incorrectly treat them as equivalent when performing lookups or comparisons.

During token verification, if AdonisJS decodes the JWT and compares claims (e.g., checking payload.username against a database key) without normalizing to a consistent form such as NFC or NFD, an attacker could exploit mismatches. Consider an authorization check that relies on a claim to look up a user or scope; if the stored key is normalized one way and the incoming token’s claim is normalized differently, the check may incorrectly grant access or fail to revoke a valid session. This class of issue intersects with the broader BOLA/IDOR and Property Authorization checks that middleBrick scans for, because the vulnerability often manifests as an authorization boundary flaw.

Additionally, if your application uses JWTs to carry metadata that influences behavior—such as scopes or roles—Unicode inconsistencies can lead to privilege confusion. An attacker might supply a decomposed form of a role name that passes a naive string comparison, allowing escalation or access to unauthorized resources. Because JWT tokens are often transmitted in URLs or headers, normalization discrepancies can also affect logging and audit trails, making it harder to correlate events. middleBrick’s 12 security checks, including Property Authorization and Input Validation, are designed to surface these inconsistencies by correlating spec definitions with runtime behavior.

Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes

To mitigate Unicode normalization issues with JWT tokens in AdonisJS, normalize all relevant strings to a single canonical form before signing, storing, or comparing. Use a well‑tested normalization library and apply it consistently across token creation and verification.

Below is a concrete example using the jwt-simple package and the Unicode normalization API available in Node.js. This approach ensures that claims such as username or sub are normalized before being included in the payload and are normalized again before comparison during verification.

import jwt from 'jwt-simple';
import { createHash } from 'crypto';

// Normalization helper using the built-in Unicode normalizer
const normalize = (input) => {
  if (typeof input !== 'string') return input;
  return input.normalize('NFC');
};

// Token generation: normalize claims before signing
const generateToken = (user) => {
  const payload = {
    sub: normalize(user.id.toString()),
    username: normalize(user.username),
    scope: normalize(user.scope), // e.g., 'read:profile' or localized role names
    iat: Math.floor(Date.now() / 1000),
  };
  const secret = process.env.JWT_SECRET;
  if (!secret) throw new Error('JWT_SECRET is required');
  return jwt.encode(payload, secret);
};

// Token verification: normalize incoming claims before comparison
const verifyToken = (token) => {
  const secret = process.env.JWT_SECRET;
  if (!secret) throw new Error('JWT_SECRET is required');
  const decoded = jwt.decode(token, secret);
  const normalizedSub = normalize(decoded.sub);
  const normalizedUsername = normalize(decoded.username);
  const normalizedScope = normalize(decoded.scope);

  // Example: lookup user by normalized username
  const user = await User.findBy('username', normalizedUsername);
  if (!user || user.id.toString() !== normalizedSub) {
    throw new Error('Invalid token');
  }

  // Use normalized scope for authorization checks
  return { user, scope: normalizedScope };
};

export { generateToken, verifyToken };

If you use AdonisJS middleware to protect routes, integrate normalization into the auth pipeline so that every request normalizes claims before they are used for authorization decisions:

import { normalize } from './unicode-helpers';

const jwtAuth = async (ctx, next) => {
  const authHeader = ctx.request.header().authorization;
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return ctx.response.unauthorized('Missing token');
  }
  const token = authHeader.split(' ')[1];
  try {
    const { user, scope } = await verifyToken(token);
    ctx.authUser = user;
    ctx.userScope = scope;
    await next();
  } catch (error) {
    ctx.response.unauthorized('Invalid token');
  }
};

export default jwtAuth;

For claims that represent identifiers used in database queries or file paths, also consider normalizing before persistence. This reduces the surface area where an attacker can leverage normalization mismatches. middleBrick’s Input Validation and Property Authorization checks can help identify endpoints where non-normalized claims are used in sensitive operations.

Finally, document the chosen normalization form (NFC is commonly recommended) and enforce it at API boundaries. If you integrate with third‑party identity providers, verify that their token claims are normalized consistently with your expectations. The Pro plan’s continuous monitoring can alert you to sudden changes in token claim patterns that may indicate attempted abuse related to Unicode handling.

Frequently Asked Questions

Does normalizing to NFC fully prevent Unicode-related token confusion in AdonisJS?
Normalizing to NFC substantially reduces risk by ensuring a single canonical form for equivalent strings, but you must also normalize before any comparison or lookup and validate that external identity providers use the same normalization form.
Can middleBrick detect Unicode normalization issues in JWT tokens?
middleBrick’s Property Authorization and Input Validation checks can surface inconsistencies where non-normalized claims are used in authorization logic, especially when spec definitions differ from runtime payloads.