hashicorp/nomad · error

failed to parse signed token: %w

Error message

failed to parse signed token: %w

What it means

Fires in Encrypter.VerifyClaim when jwt.ParseSigned cannot parse the presented token string — the token is malformed/truncated or not a valid JWS compact serialization, so signature verification never starts.

Source

Thrown at nomad/encrypter.go:362

			return "", "", err
		}
	}

	raw, err := jwt.Signed(sig).Claims(claims).CompactSerialize()
	if err != nil {
		return "", "", err
	}

	return raw, cs.rootKey.Meta.KeyID, nil
}

// VerifyClaim accepts a previously signed encoded claim and validates
// it before returning the claim.
func (e *Encrypter) VerifyClaim(tokenString string) (*structs.IdentityClaims, error) {

	token, err := jwt.ParseSigned(tokenString)
	if err != nil {
		return nil, fmt.Errorf("failed to parse signed token: %w", err)
	}

	// Find the Key ID
	keyID, err := joseutil.KeyID(token)
	if err != nil {
		return nil, err
	}

	// Find the key material
	pubKey, err := e.waitForPublicKey(keyID)
	if err != nil {
		return nil, err
	}

	typedPubKey, err := pubKey.GetPublicKey()
	if err != nil {
		return nil, err
	}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Regenerate the token (nomad alloc status / job re-submit) rather than repairing it
  2. Verify the full compact JWT (three dot-separated base64url segments) reaches VerifyClaim — no truncation or whitespace
  3. Confirm the caller is passing a signed workload identity claim, not an ACL secret ID
  4. Check secret templates for rendering bugs that cut the token

Example fix

// before: template truncates token
NOMAD_JWT = "{{ with secret "nomad/vars/foo" }}{{ .Data.token | truncate 60 "" }}{{ end }}"
// after: render the full value
NOMAD_JWT = "{{ with secret "nomad/vars/foo" }}{{ .Data.token }}{{ end }}"
Defensive patterns

Strategy: validation

Validate before calling

import "strings"
// quick shape check before VerifyClaim
func looksLikeCompactJWT(tok string) bool {
  parts := strings.Split(strings.TrimSpace(tok), ".")
  return len(parts) == 3 && len(parts[0]) > 0 && len(parts[1]) > 0 && len(parts[2]) > 0
}

Type guard

func isProbablyJWS(s string) bool {
  s = strings.TrimSpace(s)
  parts := strings.Split(s, ".")
  if len(parts) != 3 { return false }
  for _, p := range parts {
    if p == "" { return false }
    for _, c := range p {
      ok := c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
      if !ok { return false }
    }
  }
  return true
}

Try / catch

claims, err := encrypter.VerifyClaim(token)
if err != nil && strings.Contains(err.Error(), "failed to parse signed token") {
  return fmt.Errorf("token malformed (got %d chars) — re-issue the workload token: %w", len(token), err)
}

Prevention

When it happens

Trigger: Encrypter.VerifyClaim(tokenString) is called with an empty, truncated, base64-corrupt, or non-JWT string; parse fails before any key lookup.

Common situations: Workload passes a truncated NOMAD_TOKEN-style value, a config template captured only part of the token, whitespace/newlines injected by a secret template, or a caller passes an opaque ACL token where a signed identity claim was expected.

Understand the failure class

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/bba09a5cd390489b. Report an issue: GitHub.