router-for-me/CLIProxyAPI · error

home jwt certificate_id is required

Error message

home jwt certificate_id is required

What it means

Claim-level validation in parseHomeJWTClaims: the decoded JWT payload's certificate_id claim is empty or whitespace-only. The certificate_id is required because it becomes the CSR CN when issuing the client certificate during home enrollment.

Source

Thrown at internal/home/certificate.go:95

		},
	}, nil
}

func parseHomeJWTClaims(rawJWT string) (homeJWTClaims, error) {
	var claims homeJWTClaims
	parts := strings.Split(strings.TrimSpace(rawJWT), ".")
	if len(parts) != 3 {
		return claims, fmt.Errorf("home jwt is invalid")
	}
	payload, errDecode := decodeJWTPart(parts[1])
	if errDecode != nil {
		return claims, errDecode
	}
	if errUnmarshal := json.Unmarshal(payload, &claims); errUnmarshal != nil {
		return claims, errUnmarshal
	}
	if strings.TrimSpace(claims.CertificateID) == "" {
		return claims, fmt.Errorf("home jwt certificate_id is required")
	}
	if strings.TrimSpace(claims.ClusterID) == "" {
		return claims, fmt.Errorf("home jwt cluster_id is required")
	}
	if normalizeFingerprint(claims.CAFingerprint) == "" {
		return claims, fmt.Errorf("home jwt ca_fingerprint is required")
	}
	if strings.TrimSpace(claims.EnrollmentSecret) == "" {
		return claims, fmt.Errorf("home jwt enrollment_secret is required")
	}
	if strings.TrimSpace(claims.IP) == "" || claims.Port <= 0 {
		return claims, fmt.Errorf("home jwt target address is invalid")
	}
	return claims, nil
}

func decodeJWTPart(part string) ([]byte, error) {
	if decoded, errDecode := base64.RawURLEncoding.DecodeString(part); errDecode == nil {

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Request a fresh enrollment token from the home coordinator / service that minted it
  2. If you mint tokens yourself, include a non-empty certificate_id claim
  3. Decode the payload (base64url of part 2) and confirm certificate_id is present before retrying

Example fix

// before (JWT payload)
{"cluster_id":"c1","ca_fingerprint":"AA:BB.."}

// after (JWT payload)
{"certificate_id":"client-123","cluster_id":"c1","ca_fingerprint":"AA:BB.."}
Defensive patterns

Strategy: validation

Validate before calling

func hasClaim(payload []byte, key string) bool {
    var m map[string]any
    if json.Unmarshal(payload, &m) != nil {
        return false
    }
    v, ok := m[key].(string)
    return ok && strings.TrimSpace(v) != ""
}

Prevention

When it happens

Trigger: The home coordinator issued an enrollment JWT whose payload lacks (or blanks) the certificate_id claim. Decoding succeeds, so this is a token-content problem, not a token-format problem.

Common situations: A stale or mis-minted token from an older coordinator version, manual token construction that omits the claim, or a payload copied from a different JWT type.

Understand the failure class

Related errors


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/2af247ac71db4b5a. Report an issue: GitHub.