hashicorp/nomad · error

JWT login returned an empty secret

Error message

JWT login returned an empty secret

What it means

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.

Source

Thrown at client/vaultclient/vaultclient.go:162

	// Make sure the login request is not passing any token and that we're using
	// the expected namespace to login
	cc.SetToken("")
	if req.Namespace != "" {
		cc.SetNamespace(req.Namespace)
	}

	jwtLoginPath := fmt.Sprintf("auth/%s/login", c.config.JWTAuthBackendPath)
	s, err := cc.Logical().WriteWithContext(ctx, jwtLoginPath,
		map[string]any{
			"role": req.Role,
			"jwt":  req.JWT,
		},
	)
	if err != nil {
		return "", false, 0, fmt.Errorf("failed to login with JWT: %v", err)
	}
	if s == nil {
		return "", false, 0, errors.New("JWT login returned an empty secret")
	}
	if s.Auth == nil {
		return "", false, 0, errors.New("JWT login did not return a token")
	}

	for _, w := range s.Warnings {
		c.logger.Warn("JWT login warning", "warning", w)
	}

	return s.Auth.ClientToken, s.Auth.Renewable, s.Auth.LeaseDuration, nil
}

func (c *vaultClient) Renew(ctx context.Context, token string, lease int) (duration time.Duration, err error) {
	cc, err := c.Clone()
	if err != nil {
		return 0, err
	}
	cc.SetToken(token)

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Check Vault server logs for the login request and confirm a normal 200 response with a secret body
  2. Inspect any proxy/load balancer between Nomad and Vault for response rewriting or empty-body interception
  3. Verify the jwt auth backend path (JWTAuthBackendPath) points to a real, enabled auth mount
  4. Retry the login; if it persists, capture a client trace of the raw Vault response and report to Vault/Nomad

Example fix

// before
s, err := cc.Logical().WriteWithContext(ctx, jwtLoginPath, data)
if s.Auth == nil { ... } // panics if s is nil
// after
s, err := cc.Logical().WriteWithContext(ctx, jwtLoginPath, data)
if err != nil { return "", false, 0, err }
if s == nil { return "", false, 0, errors.New("JWT login returned an empty secret") }
Defensive patterns

Strategy: retry

Validate before calling

// Nothing to pre-validate client-side; ensure Vault address and JWT auth mount are configured:
if c.config.JWTAuthBackendPath == "" {
	return fmt.Errorf("vault JWT auth backend path not configured")
}

Type guard

func hasAuthSecret(s *vaultapi.Secret) bool {
	return s != nil && s.Auth != nil && s.Auth.ClientToken != ""
}

Try / catch

token, renewable, lease, err := client.DeriveTokenWithJWT(ctx, req)
if err != nil {
	if strings.Contains(err.Error(), "JWT login returned an empty secret") {
		// check Vault health/proxy, then retry with backoff
	}
	return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/4e4714d0e80a5b69. Report an issue: GitHub.