{"record":{"id":"4e4714d0e80a5b69","repo":"hashicorp/nomad","slug":"jwt-login-returned-an-empty-secret","errorCode":null,"errorMessage":"JWT login returned an empty secret","messagePattern":"JWT login returned an empty secret","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/vaultclient/vaultclient.go","lineNumber":162,"sourceCode":"\t// Make sure the login request is not passing any token and that we're using\n\t// the expected namespace to login\n\tcc.SetToken(\"\")\n\tif req.Namespace != \"\" {\n\t\tcc.SetNamespace(req.Namespace)\n\t}\n\n\tjwtLoginPath := fmt.Sprintf(\"auth/%s/login\", c.config.JWTAuthBackendPath)\n\ts, err := cc.Logical().WriteWithContext(ctx, jwtLoginPath,\n\t\tmap[string]any{\n\t\t\t\"role\": req.Role,\n\t\t\t\"jwt\":  req.JWT,\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn \"\", false, 0, fmt.Errorf(\"failed to login with JWT: %v\", err)\n\t}\n\tif s == nil {\n\t\treturn \"\", false, 0, errors.New(\"JWT login returned an empty secret\")\n\t}\n\tif s.Auth == nil {\n\t\treturn \"\", false, 0, errors.New(\"JWT login did not return a token\")\n\t}\n\n\tfor _, w := range s.Warnings {\n\t\tc.logger.Warn(\"JWT login warning\", \"warning\", w)\n\t}\n\n\treturn s.Auth.ClientToken, s.Auth.Renewable, s.Auth.LeaseDuration, nil\n}\n\nfunc (c *vaultClient) Renew(ctx context.Context, token string, lease int) (duration time.Duration, err error) {\n\tcc, err := c.Clone()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tcc.SetToken(token)","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/client/vaultclient/vaultclient.go#L144-L180","documentation":"Raised by DeriveTokenWithJWT in client/vaultclient/vaultclient.go when the Vault JWT auth login (Logical().WriteWithContext on auth/<backend>/login) succeeds without an error but returns a nil/empty Secret. Vault normally always returns a secret on a successful login, so an empty one indicates an unexpected or malformed server response. The client guards against it to avoid a nil-pointer dereference on s.Auth.","triggerScenarios":"Vault's auth/<mount>/login endpoint returns HTTP 204 or an empty body; a proxy/load-balancer strips the response; a misbehaving Vault dev server or snapshot returns an empty secret payload for a successful request.","commonSituations":"Reverse proxies or service meshes (Envoy, Consul mesh) intercepting Vault responses; Vault cluster behind a misconfigured load balancer health-check rewriting responses; JWT auth backend misconfiguration returning empty auth blocks in unusual mounts.","solutions":["Check Vault server logs for the login request and confirm a normal 200 response with a secret body","Inspect any proxy/load balancer between Nomad and Vault for response rewriting or empty-body interception","Verify the jwt auth backend path (JWTAuthBackendPath) points to a real, enabled auth mount","Retry the login; if it persists, capture a client trace of the raw Vault response and report to Vault/Nomad"],"exampleFix":"// before\ns, err := cc.Logical().WriteWithContext(ctx, jwtLoginPath, data)\nif s.Auth == nil { ... } // panics if s is nil\n// after\ns, err := cc.Logical().WriteWithContext(ctx, jwtLoginPath, data)\nif err != nil { return \"\", false, 0, err }\nif s == nil { return \"\", false, 0, errors.New(\"JWT login returned an empty secret\") }","handlingStrategy":"retry","validationCode":"// Nothing to pre-validate client-side; ensure Vault address and JWT auth mount are configured:\nif c.config.JWTAuthBackendPath == \"\" {\n\treturn fmt.Errorf(\"vault JWT auth backend path not configured\")\n}","typeGuard":"func hasAuthSecret(s *vaultapi.Secret) bool {\n\treturn s != nil && s.Auth != nil && s.Auth.ClientToken != \"\"\n}","tryCatchPattern":"token, renewable, lease, err := client.DeriveTokenWithJWT(ctx, req)\nif err != nil {\n\tif strings.Contains(err.Error(), \"JWT login returned an empty secret\") {\n\t\t// check Vault health/proxy, then retry with backoff\n\t}\n\treturn err\n}","preventionTips":["Keep no proxy/LB between Nomad and Vault that can rewrite or drop response bodies","Verify the auth mount responds with a full secret by testing `vault write auth/<mount>/login ...` directly","Monitor Vault health endpoints before issuing JWT logins"],"tags":["vault","jwt","auth","empty-response"],"backgroundTag":"vault-jwt-login-failed","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}