{"record":{"id":"9eb82f4396e4bf7d","repo":"nats-io/nats-server","slug":"auth-callout-service-returned-an-error-v","errorCode":null,"errorMessage":"auth callout service returned an error: %v","messagePattern":"auth callout service returned an error: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/auth_callout.go","lineNumber":137,"sourceCode":"\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}\n\n\t\t// check the audience to be the server ID\n\t\tif cr.Audience != s.info.ID {\n\t\t\treturn nil, errors.New(\"auth callout violation: auth callout response is not for server\")\n\t\t}\n\n\t\t// check if had an error message from the auth account\n\t\tif cr.Error != _EMPTY_ {\n\t\t\treturn nil, fmt.Errorf(\"auth callout service returned an error: %v\", cr.Error)\n\t\t}\n\n\t\t// if response is encrypted none of this is needed\n\t\tif isOperatorMode && !encrypted {\n\t\t\tpkStr := cr.Issuer\n\t\t\tif cr.IssuerAccount != _EMPTY_ {\n\t\t\t\tpkStr = cr.IssuerAccount\n\t\t\t}\n\t\t\tif pkStr != account {\n\t\t\t\tif _, ok := acc.hasIssuer(pkStr); !ok {\n\t\t\t\t\treturn nil, errors.New(\"auth callout signing key is unknown\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn jwt.DecodeUserClaims(cr.Jwt)\n\t}\n","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/auth_callout.go#L119-L155","documentation":"The auth callout response JWT itself contained a non-empty Error field, meaning the callout service explicitly refused or failed the authorization request. The server surfaces that error text verbatim to the connecting client's auth flow. This is the designed mechanism for a callout to deny a connection with a reason.","triggerScenarios":"The callout service publishes an AuthorizationResponseClaims with cr.Error set to a non-empty message (e.g. \"user not found\", \"account suspended\") in reply to an authorization request.","commonSituations":"User credentials rejected by the external auth backend; upstream identity provider outage causing the callout to report an error; misconfigured callout rules denying valid users.","solutions":["Read the %v message — it is the callout service's own denial reason — and fix credentials/permissions on the client or rules in the callout service.","Check the auth callout service's logs to see why it set cr.Error.","Verify the external identity backend the callout consults is healthy.","If the denial is wrong, update the callout service's authorization logic or account mapping."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"auth callout service returned an error\") {\n    // denial reason follows ': ' — surface it to the operator/client\n    log.Printf(\"callout denied connection: %s\", err)\n}","preventionTips":["Inspect the callout service's own error message in the wrapped error.","Monitor the callout's upstream identity backend health.","Log callout decisions server-side to correlate denials with clients."],"tags":["auth-callout","authorization-denied"],"backgroundTag":"auth-callout-denied","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}