nats-io/nats-server · error

authorization response had validation errors

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.

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.

Appendix: 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)