hashicorp/nomad · warning

handshake auth token mismatched auth header token

Error message

handshake auth token mismatched auth header token

What it means

When ws_handshake is enabled, the client must send its auth token both in the HTTP header and in the first handshake message. readWsHandshake rejects the connection when the header token and the handshake-message token differ, as a mismatch may indicate CSRF-style header injection from a browser or a compromised/misconfigured client.

Source

Thrown at command/agent/websockets.go:162

	} else if h, err := strconv.ParseBool(hv); err != nil {
		return "", fmt.Errorf("ws_handshake value is not a boolean: %v", err)
	} else if !h {
		return "", nil
	}

	// verify that any header token set by a non-browser client agrees with the
	// auth header
	reqToken := new(string)
	s.parseToken(req, reqToken)

	var h wsHandshakeMessage
	err := readFn(&h)
	if err != nil {
		return "", err
	}

	if reqToken != nil && *reqToken != "" && *reqToken != h.AuthToken {
		return "", fmt.Errorf("handshake auth token mismatched auth header token")
	}

	supportedWSHandshakeVersion := 1
	if h.Version != supportedWSHandshakeVersion {
		return "", fmt.Errorf("unexpected handshake value: %v", h.Version)
	}

	return h.AuthToken, nil
}

// getWebsocketConnection retrieves the websocket connection from context
func (s *HTTPServer) getWebsocketConnection(req *http.Request) (*websocket.Conn, error) {
	ctx := req.Context()

	// Get websocket connection from context (set by audit wrapper)
	connRaw := ctx.Value(ctxKeyWebSocketConn)
	if connRaw == nil {
		return nil, fmt.Errorf("websocket connection not found in context")

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Send the identical auth token in both the HTTP auth header and the websocket handshake message.
  2. Refresh the client so both tokens derive from the same current ACL token.
  3. Clear cached pages/scripts holding stale tokens and reload the UI.
  4. If a proxy manipulates headers, configure it to pass the auth header through unchanged.

Example fix

// before
msg := handshakeMessage{AuthToken: oldToken}
// after
msg := handshakeMessage{AuthToken: currentACLToken} // same value as auth header
Defensive patterns

Strategy: validation

Validate before calling

if headerToken != "" && handshakeToken != headerToken {
    return fmt.Errorf("handshake and header tokens must match")
}

Try / catch

if err := readFn(&h); err != nil {
    if strings.Contains(err.Error(), "mismatched auth header token") {
        refreshTokenAndRetry()
    }
    return err
}

Prevention

When it happens

Trigger: readFn decodes a handshake message whose AuthToken differs from the auth header token (reqToken) when reqToken is non-empty — e.g. a browser page that cannot set headers relaying a stale token.

Common situations: Browser-based exec where the handshake token was generated from an old ACL token while the page holds a newer header token; proxies that strip or rewrite auth headers; replayed handshake payloads.

Understand the failure class

Related errors


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