{"record":{"id":"c00e02996bfb2b3b","repo":"nats-io/nats-server","slug":"error-decrypting-auth-callout-response-on-account","errorCode":null,"errorMessage":"error decrypting auth callout response on account %q: %v","messagePattern":"error decrypting auth callout response on account %q: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/auth_callout.go","lineNumber":110,"sourceCode":"\tdecodeResponse := func(rc *client, rmsg []byte, acc *Account) (*jwt.UserClaims, error) {\n\t\taccount := acc.Name\n\t\t_, msg := rc.msgParts(rmsg)\n\n\t\t// This signals not authorized.\n\t\t// Since this is an account subscription will always have \"\\r\\n\".\n\t\tif len(msg) <= LEN_CR_LF {\n\t\t\treturn nil, fmt.Errorf(\"auth callout violation: %q on account %q\", \"no reason supplied\", account)\n\t\t}\n\t\t// Strip trailing CRLF.\n\t\tmsg = msg[:len(msg)-LEN_CR_LF]\n\t\tencrypted := false\n\t\t// If we sent an encrypted request the response could be encrypted as well.\n\t\t// we are expecting the input to be `eyJ` if it is a JWT\n\t\tif xkp != nil && len(msg) > 0 && !bytes.HasPrefix(msg, []byte(jwtPrefix)) {\n\t\t\tvar err error\n\t\t\tmsg, err = xkp.Open(msg, pubAccXKey)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error decrypting auth callout response on account %q: %v\", account, err)\n\t\t\t}\n\t\t\tencrypted = true\n\t\t}\n\n\t\tcr, err := jwt.DecodeAuthorizationResponseClaims(string(msg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvr := jwt.CreateValidationResults()\n\t\tcr.Validate(vr)\n\t\tif len(vr.Issues) > 0 {\n\t\t\treturn nil, fmt.Errorf(\"authorization response had validation errors: %v\", vr.Issues[0])\n\t\t}\n\n\t\t// the subject is the user id\n\t\tif cr.Subject != pub {\n\t\t\treturn nil, errors.New(\"auth callout violation: auth callout response is not for expected user\")\n\t\t}","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/auth_callout.go#L92-L128","documentation":"The server received an auth callout response that should be encrypted (because the request was encrypted with the account's xkey) but failed to decrypt it using the xkey key pair. The raw encrypted payload could not be opened, so the authorization decision cannot be read.","triggerScenarios":"During response handling, when the request was sent with xkp != nil and the response does not start with the JWT prefix 'eyJ', the library attempts xkp.Open(msg, pubAccXKey). Any decryption failure (wrong key, corrupted payload, non-XKey data) returns this error wrapping the crypto failure and the account name.","commonSituations":"Auth callout service encrypting the response with the wrong public xkey (rotated or mismatched keys), sending plaintext that doesn't begin with 'eyJ', payload truncated by middleware, or key rotation on the account without restarting the callout service.","solutions":["Ensure the callout service encrypts responses with the account's current public xkey published on the account claims.","Rotate keys coherently: after changing the account xkey, restart/redeploy the callout service with the new key.","Log the crypto error detail in the wrapped %v to identify mismatched-key vs corrupted-data cases.","Verify the response is not raw plaintext; it must be encrypted or a JWT starting with 'eyJ'."],"exampleFix":"// before (callout service)\nnc.Publish(msg.Reply, []byte(\"denied: bad token\"))\n// after\nsealed, _ := xkey.Seal([]byte(respJWT), accPubXKey, svcXKey)\nnc.Publish(msg.Reply, sealed)","handlingStrategy":"try-catch","validationCode":"// in the auth callout service: confirm the account's public xkey before sealing\naccJWT, _ := fetchAccountJWT(accPubKey)\naccClaims, _ := jwt.DecodeAccountClaims(accJWT)\nif len(accClaims.EncryptionKeys) == 0 { return errors.New(\"no xkey on account; do not encrypt\") }","typeGuard":"func hasXKey(ac *jwt.AccountClaims) bool { return len(ac.EncryptionKeys) > 0 }","tryCatchPattern":"user, err := s.lookupAccountForClientRequest(...)\nif err != nil && strings.Contains(err.Error(), \"error decrypting auth callout response\") {\n    log.Printf(\"callout response decryption failed: %v\", err)\n    return nil, ErrAuthentication\n}","preventionTips":["Keep the callout service's copy of the account xkey in sync with rotations","Only encrypt responses when the request was encrypted","Log the wrapped crypto error to distinguish key mismatch vs corruption","Test callout encryption round-trip in CI"],"tags":["auth-callout","encryption","xkey","nats"],"backgroundTag":"auth-callout-decrypt-failed","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}