nats-io/nats-server · error

wrong issuer for auth callout response on account %q, expect

Error message

wrong issuer for auth callout response on account %q, expected %q got %q

What it means

In non-operator mode, the Issuer of the auth callout response's user JWT does not match the expected issuer (the account/signing key the server expects for that account). The server rejects the response because the JWT was not signed by the authorized identity. This prevents a callout from minting users attributed to another issuer.

Source

Thrown at server/auth_callout.go:182

			// Operator mode is who we send the request on unless switching accounts.
			issuer = acc.Name
		}

		// the jwt issuer can be a signing key
		jwtIssuer := arc.Issuer
		if arc.IssuerAccount != _EMPTY_ {
			if !isOperatorMode {
				// this should be invalid - effectively it would allow the auth callout
				// to issue on another account which may be allowed given the configuration
				// where the auth callout account can handle multiple different ones..
				return _EMPTY_, fmt.Errorf("error non operator mode account %q: attempted to use issuer_account", account)
			}
			jwtIssuer = arc.IssuerAccount
		}

		if jwtIssuer != issuer {
			if !isOperatorMode {
				return _EMPTY_, fmt.Errorf("wrong issuer for auth callout response on account %q, expected %q got %q", account, issuer, jwtIssuer)
			} else if !acc.isAllowedAcount(jwtIssuer) {
				return _EMPTY_, fmt.Errorf("account %q not permitted as valid account option for auth callout for account %q",
					arc.Issuer, account)
			}
		}
		return jwtIssuer, nil
	}

	getExpirationAndAllowedConnections := func(arc *jwt.UserClaims, account string) (time.Duration, map[string]struct{}, error) {
		allowNow, expiration := validateTimes(arc)
		if !allowNow {
			c.Errorf("Outside connect times")
			return 0, nil, fmt.Errorf("authorized user on account %q outside of valid connect times", account)
		}

		allowedConnTypes, err := convertAllowedConnectionTypes(arc.User.AllowedConnectionTypes)
		if err != nil {
			c.Debugf("%v", err)

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Sign the user JWT with the issuer (account or its signing key) the server expects for the target account.
  2. Update the callout's configured signing key to match the account's current issuer after a key rotation.
  3. Check the error's expected/got values and align the callout's key selection.
  4. If multiple issuers are intended, switch to operator mode where allowed-account checks apply instead.

Example fix

// before
signed, err := arc.Encode(calloutServiceNkey)
// after
signed, err := arc.Encode(targetAccountIssuerNkey) // must equal the account's issuer in non-operator mode
Defensive patterns

Strategy: validation

Validate before calling

if jwtIssuer := arc.Issuer; jwtIssuer != expectedIssuer {
    return fmt.Errorf("issuer %q != expected %q", jwtIssuer, expectedIssuer)
}

Prevention

When it happens

Trigger: The callout signs the user JWT with a signing key/nkey different from the account's expected issuer while the server runs in non-operator mode, so jwtIssuer != issuer.

Common situations: Callout configured with the wrong signing key or signing with the callout account key instead of the target account's key; rotating keys on the account without updating the callout.

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/eb5ca4a82a8ab6bd. Report an issue: GitHub.