Short answer: have the Node backend authorize a tenant-scoped object key and return a presigned upload URL; let the Vue browser upload with Axios progress events, then have the backend verify the key before attaching it to a record. The bytes should not pass through your application server, but the authorization decision must.
This is an architecture decision record, not a claim that one storage product fits every workload. The invariants are simple: a logged-in user can write only inside a server-derived prefix, an upload is not “attached” until storage confirms it, and a private object is read through a newly signed URL or a backend proxy. The failure boundary is equally clear: a presigned URL is a bearer capability until it expires.
What should the browser, Node API, and private bucket each own?
The browser asks for permission with a filename and content type. The Node API authenticates the session, creates a key such as tenant/acme/user/42/uuid.pdf , and requests a presign for that exact key.…