dgraph-io/dgraph · error

unexpected signing method: Expected %s Found %s

Error message

unexpected signing method: Expected %s Found %s

What it means

The `alg` header inside the incoming JWT does not match the configured a.Algo. This is an algorithm-confusion guard: it refuses tokens signed with a different algorithm than the one you explicitly configured, preventing attackers from downgrading e.g. RS256 to HS256/none. The expected and actual algorithms are included in the message.

Source

Thrown at graphql/authorization/auth.go:396

	var token *jwt.Token
	var err error
	// Verification through JWKUrl
	if len(a.JWKUrls) != 0 {
		token, err = a.validateThroughJWKUrl(jwtStr)
	} else {
		if a.Algo == "" {
			return nil, fmt.Errorf(
				"jwt token cannot be validated because verification algorithm is not set")
		}

		// The JWT library supports comparison of `aud` in JWT against a single string. Hence, we
		// disable the `aud` claim verification at the library end using `WithoutAudienceValidation` and
		// use our custom validation function `validateAudience`.
		token, err =
			jwt.ParseWithClaims(jwtStr, &CustomClaims{authMeta: a}, func(token *jwt.Token) (interface{}, error) {
				algo, _ := token.Header["alg"].(string)
				if algo != a.Algo {
					return nil, errors.Errorf("unexpected signing method: Expected %s Found %s",
						a.Algo, algo)
				}

				switch a.SigningMethod.(type) {
				case *jwt.SigningMethodHMAC:
					return []byte(a.VerificationKey), nil
				case *jwt.SigningMethodRSA:
					return a.RSAPublicKey, nil
				}

				return nil, errors.Errorf("couldn't parse signing method from token header: %s", algo)
			})
	}

	if err != nil {
		return nil, errors.Errorf("unable to parse jwt token:%v", err)
	}

View on GitHub (pinned to 759e242be6)

Solutions

  1. Make the token issuer sign with the same algorithm configured in a.Algo; redeploy/rotate tokens if needed.
  2. Update a.Algo to match the issuer's actual algorithm if the algorithm change was intentional.
  3. Check the token's header (decode first JWT segment) to confirm its alg value.
  4. Reject/monitor repeated mismatches — they may indicate attempted algorithm-confusion attacks.
  5. Ensure both issuer and verifier config come from the same source of truth.

Example fix

// before
auth := &authorization.AuthOptions{ Algo: "RS256", ... } // token is HS256
// after (issuer switched to HS256 intentionally)
auth := &authorization.AuthOptions{ Algo: "HS256", SigningMethod: jwt.SigningMethodHS256, VerificationKey: sharedSecret }
Defensive patterns

Strategy: validation

Validate before calling

func algMatches(jwtStr, expected string) bool {
    parts := strings.Split(jwtStr, ".")
    if len(parts) != 3 { return false }
    hdr, err := base64.RawURLEncoding.DecodeString(parts[0])
    if err != nil { return false }
    var h struct{ Alg string `json:"alg"` }
    json.Unmarshal(hdr, &h)
    return h.Alg == expected
}

Try / catch

if _, err := auth.ExtractCustomClaims(ctx, jwtStr); err != nil && strings.Contains(err.Error(), "unexpected signing method") {
    return nil, status.Error(codes.Unauthenticated, "token algorithm not accepted")
}

Prevention

When it happens

Trigger: In validateJWTCustomClaims with no JWKUrls: jwt.ParseWithClaims keyfunc reads token.Header["alg"] and it differs from a.Algo — e.g. configured "RS256" but token says "HS256", or alg header missing (algo="").

Common situations: Tokens issued by a signer reconfigured to a different algorithm while verifiers still expect the old one; token from wrong issuer/environment; attacker-supplied token with alg none or HS256 (the guard doing its job); alg header absent from a hand-crafted token.

Related errors


AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01). Data as JSON: /api/errors/181a0994304e5d21. Report an issue: GitHub.