nats-io/nats-server · error

authorization response had validation errors: %v

Error message

authorization response had validation errors: %v

What it means

The server validated the authorization response claims (AuthorizationResponseClaims, cr) returned by an auth callout service using jwt.CreateValidationResults/Validate, and at least one generic JWT claim was invalid (only the first issue is reported). This is thrown when the callout's response JWT fails structural/temporal validation (e.g. expired, malformed claims) before the server will trust it. The auth callout contract requires a well-formed, currently valid response JWT.

Source

Thrown at server/auth_callout.go:122

		// If we sent an encrypted request the response could be encrypted as well.
		// we are expecting the input to be `eyJ` if it is a JWT
		if xkp != nil && len(msg) > 0 && !bytes.HasPrefix(msg, []byte(jwtPrefix)) {
			var err error
			msg, err = xkp.Open(msg, pubAccXKey)
			if err != nil {
				return nil, fmt.Errorf("error decrypting auth callout response on account %q: %v", account, err)
			}
			encrypted = true
		}

		cr, err := jwt.DecodeAuthorizationResponseClaims(string(msg))
		if err != nil {
			return nil, err
		}
		vr := jwt.CreateValidationResults()
		cr.Validate(vr)
		if len(vr.Issues) > 0 {
			return nil, fmt.Errorf("authorization response had validation errors: %v", vr.Issues[0])
		}

		// the subject is the user id
		if cr.Subject != pub {
			return nil, errors.New("auth callout violation: auth callout response is not for expected user")
		}

		// check the audience to be the server ID
		if cr.Audience != s.info.ID {
			return nil, errors.New("auth callout violation: auth callout response is not for server")
		}

		// check if had an error message from the auth account
		if cr.Error != _EMPTY_ {
			return nil, fmt.Errorf("auth callout service returned an error: %v", cr.Error)
		}

		// if response is encrypted none of this is needed

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Sync the callout service's clock (NTP) so issued JWTs are temporally valid.
  2. Log vr.Issues server-side or replicate cr.Validate(vr) in the callout to see the exact failing claim and fix claim construction.
  3. Ensure the callout library/jwt package version matches what the server validates against.
  4. Set proper Issuer, Subject, Audience and times in the AuthorizationResponseClaims before signing.

Example fix

// before
cr := jwt.NewAuthorizationResponseClaims(pub, issuer)
cr.Expires = time.Now().Add(-time.Minute) // stale/expired
// after
cr := jwt.NewAuthorizationResponseClaims(pub, issuer)
cr.Expires = time.Now().Add(2 * time.Minute)
vr := jwt.CreateValidationResults()
cr.Validate(vr)
if len(vr.Issues) > 0 { /* fix claims before publishing */ }
Defensive patterns

Strategy: validation

Validate before calling

vr := jwt.CreateValidationResults()
cr.Validate(vr)
if len(vr.Issues) > 0 {
    return fmt.Errorf("response claims invalid: %v", vr.Issues)
}

Try / catch

resp, err := doAuthCallout()
if err != nil {
    var verr *ValidationErr
    if errors.As(err, &verr) { /* regenerate claims, resync clock */ }
    return err
}

Prevention

When it happens

Trigger: An auth callout service publishes an AuthorizationResponseClaims JWT whose Validate() reports issues: expired or not-yet-valid nbf, missing/invalid issuer or subject, bad times format, or a claim violating jwt generic claim rules.

Common situations: Callout service clock skew producing expired/early JWTs; callout built against an older JWT library emitting claims the server's jwt package rejects; hand-rolled claim construction leaving required fields empty.

Related errors


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