crowdsecurity/crowdsec · error

authenticate watcher (%s): %w

Error message

authenticate watcher (%s): %w

What it means

InitLAPIClient authenticates the watcher against the LAPI with machine ID and password to obtain a JWT. When the AuthenticateWatcher call fails (any HTTP error response or transport failure), the error is wrapped with the login name so the developer knows which watcher failed. It is a client-side wrapper around a failed watcher login exchange.

Source

Thrown at pkg/apiclient/client.go:129

	client := NewClient(&Config{
		MachineID:     login,
		Password:      pwd,
		URL:           apiURL,
		PapiURL:       papiURL,
		VersionPrefix: "v1",
		UpdateScenario: func(_ context.Context) ([]string, error) {
			return scenarios, nil
		},
	})

	authResp, _, err := client.Auth.AuthenticateWatcher(ctx, models.WatcherAuthRequest{
		MachineID: &login,
		Password:  &pwd,
		Scenarios: scenarios,
	})
	if err != nil {
		return fmt.Errorf("authenticate watcher (%s): %w", login, err)
	}

	var expiration time.Time
	if err := expiration.UnmarshalText([]byte(authResp.Expire)); err != nil {
		return fmt.Errorf("unable to parse jwt expiration: %w", err)
	}

	client.GetClient().Transport.(*JWTTransport).Token = authResp.Token
	client.GetClient().Transport.(*JWTTransport).Expiration = expiration

	lapiClient = client

	return nil
}

func GetLAPIClient() (*ApiClient, error) {
	if lapiClient == nil {
		return nil, errors.New("client not initialized")

View on GitHub (pinned to 909b515798)

Solutions

  1. Verify the machine is registered on the LAPI host: run 'cscli machines list' there; if missing, re-register with 'cscli lapi register' or delete/recreate the credentials
  2. Check api_url / lapi credentials in config for typos, wrong port, or stale password
  3. Test connectivity: curl the LAPI /health endpoint from the client host; fix network/firewall/TLS issues
  4. Inspect the inner wrapped error for the actual HTTP status or transport cause (e.g. 401 = bad password, connection refused = LAPI down)

Example fix

// before
if err != nil {
    return fmt.Errorf("authenticate watcher (%s): %w", login, err)
}
// after (caller-side guard: check credentials before InitLAPIClient)
if login == "" || password == "" {
    return errors.New("empty lapi login or password in configuration")
}
if err != nil {
    return fmt.Errorf("authenticate watcher (%s): %w", login, err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// verify credentials and LAPI reachability before InitLAPIClient
if login == "" || password == "" {
    return errors.New("missing lapi credentials")
}
resp, err := http.Get(apiUrl + "/health")
if err != nil || resp.StatusCode != 200 {
    return fmt.Errorf("LAPI not reachable at %s", apiUrl)
}

Type guard

func isAuthFailure(err error) bool {
    var apiErr *apiclient.APIError
    if errors.As(err, &apiErr) {
        return apiErr.StatusCode == http.StatusUnauthorized || apiErr.StatusCode == http.StatusForbidden
    }
    return false
}

Try / catch

if err := apiclient.InitLAPIClient(ctx, apiUrl, papiUrl, login, pwd, scenarios); err != nil {
    var apiErr *apiclient.APIError
    if errors.As(err, &apiErr) && apiErr.StatusCode == 401 {
        // re-register machine or refresh credentials
    }
    return fmt.Errorf("LAPI auth failed: %w", err)
}

Prevention

When it happens

Trigger: client.Auth.AuthenticateWatcher(ctx, models.WatcherAuthRequest{...}) returns a non-nil error: the machine does not exist on LAPI, the password is wrong, the LAPI is unreachable, TLS verification fails, or the server returns 4xx/5xx.

Common situations: Machine was deleted from LAPI ('cscli machines delete' or DB reset) while crowdsec config still references it; wrong api_url or api_key in config.yaml; password mismatch after manual registration; LAPI not running or wrong port; certificate/TLS misconfig.

Understand the failure class

Related errors


AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06). Data as JSON: /api/errors/04313cca2de63f58. Report an issue: GitHub.