Skip to content
Ashish.
All posts
Diagram illustrating the alg confusion attack vector between RS256 and HS256.
6 min readSecurityBackend EngineersFeatured#jws#alg confusion#rs256#hs256#none algorithm#jwt signing#kid#jwt security

JWT Signing: JWS Algorithms and alg Confusion

An examination of JWS algorithms and alg confusion vulnerabilities in JWTs, covering RS256, HS256, and the none algorithm risks.

By Ashish KumarPart 7 of JWT From the Spec Up

Signing and Verification: JWS Algorithms and alg Confusion

In the ecosystem of JSON Web Tokens (JWT), the security model relies on the integrity of the alg (algorithm) header. This header tells the verifier which cryptographic primitive to use when validating the signature. For backend engineers, the danger lies not in the mathematics of RSA or ECDSA, but in the implementation logic that allows the alg header to dictate the verification method dynamically. When a server trusts the alg value sent by the client without strict pre-validation, it creates a class of vulnerabilities known as "alg confusion."

This article examines the mechanics of JWS signing, the specific failure modes of algorithm confusion between symmetric and asymmetric schemes, and the risks associated with the none algorithm.

The JWS Signing Mechanism

A JSON Web Signature (JWS) consists of three parts: Header, Payload, and Signature, separated by dots. The header contains metadata, most critically the alg field. As defined in RFC 7515, the JWS Compact Serialization is the primary mechanism for transmitting signed content.

{
  "alg": "RS256",
  "typ": "JWT"
}

When a server signs a token using RS256, it uses an RSA private key to sign the base64url-encoded header and payload. The resulting signature is created using PKCS#1 v1.5 padding with SHA-256, as specified in RFC 7518. The verifier then uses the corresponding RSA public key to verify that the signature matches the content.

The critical mechanism here is trust asymmetry. In asymmetric cryptography, the public key can be shared widely, but only the private key holder can generate valid signatures. In symmetric cryptography, such as HS256 (HMAC with SHA-256), the same secret key is used for both signing and verifying.

The alg header is not merely informational; it is a directive. If the verifier processes the alg header naively, it changes its entire verification strategy based on what the client says.

The RS256 to HS256 Confusion Vector

The most prevalent alg confusion vulnerability involves mixing asymmetric (RS256) and symmetric (HS256) algorithms. This attack exploits the fact that many JWT libraries accept the alg value from the token header rather than enforcing a server-side configuration. This specific vector is well-documented in security advisories regarding JWT parsing libraries, where the assumption that alg matches the key type is violated.

Consider a scenario with three actors:

  1. Alice: The legitimate issuer using RS256.
  2. Bob: The victim server verifying Alice's tokens.
  3. Eve: The attacker.

Alice issues a token signed with her RSA private key. The header specifies "alg": "RS256". Bob receives this token. Bob has access to Alice's RSA public key to verify the signature.

Eve intercepts the token. She modifies the header to "alg": "HS256". She then calculates an HMAC-SHA256 signature over the modified header and payload, using Alice's RSA public key as the secret.

// Pseudocode for the attack
const header = '{"alg":"HS256","typ":"JWT"}';
const payload = '{"sub":"eve","role":"admin"}';
const signingInput = base64urlEncode(header) + "." + base64urlEncode(payload);
 
// Eve uses Alice's public key as the HMAC secret
const signature = hmac_sha256(signingInput, alice_public_key);
const forgedToken = signingInput + "." + base64urlEncode(signature);

If Bob's verification library trusts the alg: HS256 header, it will attempt to verify the signature using HMAC-SHA256. It will use the public key it has stored for Alice (which it normally uses for RSA verification) as the HMAC secret. Since Eve used that same public key to create the HMAC signature, the verification succeeds.

Bob is now authenticated as Eve.

This failure occurs because the verifier did not enforce that tokens from Alice must use RS256. It allowed the client to dictate the algorithm. The root cause is a lack of strict configuration: the server should map the issuer (or the kid header) to a specific expected algorithm, rather than inferring it from the token.

The none Algorithm Risk

Another critical vulnerability involves the alg: none value. The JWT specification allows the alg header to be set to none, indicating that no signature is applied. As noted in RFC 7518 Section 3.6, use of the none algorithm is explicitly restricted, since it provides no integrity protection and could allow content to be maliciously modified without detection.

While some OpenID Connect (OIDC) providers use none for unauthenticated discovery endpoints, allowing it in production authentication flows is dangerous. If a server accepts a token with "alg": "none", it typically skips signature verification entirely.

An attacker can take any valid JWT, remove the signature portion, and set the algorithm to none.

GET /api/resource HTTP/1.1
Authorization: Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiJ9.

If the backend library does not explicitly reject alg: none, it will parse the header, see no signature required, and grant access based on the payload claims. This is particularly risky in systems that rely on JWTs for session management, where an attacker might simply replay a stolen token header with a manipulated payload and no signature.

Modern best practices dictate that none should never be included in the list of acceptable algorithms for any endpoint requiring authentication.

Mitigation: Strict Configuration

The solution to alg confusion is not to change the cryptographic primitives, but to enforce strict configuration on the verifier. The server must maintain a mapping of trusted issuers (or kid values) to specific algorithms. This approach is recommended by the OWASP JWT Security Cheat Sheet, which emphasizes explicit algorithm whitelisting.

When a token arrives:

  1. Parse the header to extract alg and kid.
  2. Look up the kid in the local configuration.
  3. Retrieve the expected algorithm (e.g., RS256) and the public key.
  4. Compare the header's alg with the expected algorithm.
  5. If they do not match, reject the token immediately.
// Secure verification pattern
const expectedAlg = keyConfig[kid].algorithm; // e.g., 'RS256'
const providedAlg = jwtHeader.alg;
 
if (expectedAlg !== providedAlg) {
  throw new Error('Algorithm mismatch');
}
 
// Proceed with verification using the correct primitive
verify(jwt, publicKey, { algorithms: [expectedAlg] });

By hardcoding the expected algorithm per key or issuer, the attacker cannot switch from RS256 to HS256 because the verifier will reject the token before attempting verification. Similarly, none is rejected because it does not match RS256.

Conclusion

JWT security failures are rarely due to broken cryptography. They are due to ambiguous trust models. The alg header is a security-critical configuration parameter, not free text. Backend engineers must treat the alg value as untrusted input that must be validated against a server-side policy. By binding keys to specific algorithms and rejecting deviations, systems can prevent alg confusion attacks and the risks associated with the none algorithm. This approach ensures that the mechanism of verification aligns with the intended security policy, regardless of what the client sends, as supported by OWASP and IETF security guidelines.

Related posts