Skip to content

Local media upload fails with NOT_SECURED=true: uploader uses cookie auth while the rest of the app uses header auth #1958

Description

@johnhenry

Summary

With STORAGE_PROVIDER=local and NOT_SECURED=true, uploading media from the Media library always fails. Uppy reports:

[Uppy] This looks like a network error, the endpoint might be blocked by an internet provider or a firewall.
[Uppy] TypeError: Cannot read properties of undefined (reading 'error')

The message points at networking, which is misleading — the backend is reachable and healthy. The actual cause is an auth/CORS mismatch, and nothing in the error hints at that.

Cause

NOT_SECURED=true switches the app from cookie auth to header auth. Two things follow from it:

  1. apps/backend/src/main.ts drops credentials: true from the CORS config:
cors: {
  ...(!process.env.NOT_SECURED ? { credentials: true } : {}),
  1. Auth moves to the auth header, which is added to allowedHeaders/exposedHeaders in the same block, and the auth cookie is set client-side (not httpOnly).

But the local uploader still asks for cookie credentials — libraries/react-shared-libraries/src/helpers/uppy.upload.ts:

case 'local':
  return {
    plugin: XHRUpload,
    options: {
      endpoint: `${backendUrl}/media/upload-server`,
      withCredentials: true,
    },
  };

So in header-auth mode the upload sends a cookie the server is not using, against a CORS policy that forbids credentials — the browser blocks the response, and Uppy surfaces it as a generic network error.

Worth noting getUppyUploadPlugin already receives the authenticated fetch as its second parameter, and the cloudflare branch uses it in five places via fetchUploadApiEndpoint. The local branch is the only uploader that ignores it.

Reproduce

  1. .env: STORAGE_PROVIDER="local", NOT_SECURED="true", frontend and backend on different origins (the default 4200 / 3000 split).
  2. Start the app, sign in, open Media.
  3. Upload any valid file.

Expected: the file uploads. Actual: Uppy reports a network error and the library stays empty.

Confirming it is CORS rather than networking:

$ curl -si -X OPTIONS http://localhost:3000/media/upload-server \
    -H 'Origin: http://localhost:4200' -H 'Access-Control-Request-Method: POST' \
    | grep -i access-control-allow-credentials
# (no output with NOT_SECURED=true; present without it)

Suggested fix

The two modes can be told apart without new configuration: in cookie-auth mode the auth cookie is set httpOnly: true (users.controller.ts) and is therefore unreadable from JS, while in NOT_SECURED mode the frontend sets it client-side and it is readable. So:

const readAuthCookie = () =>
  typeof document === 'undefined'
    ? undefined
    : document.cookie.match(/(?:^|;\s*)auth=([^;]+)/)?.[1];

case 'local': {
  // httpOnly in cookie-auth mode, so a readable `auth` cookie means the app is
  // running in NOT_SECURED (header auth) mode, where CORS forbids credentials.
  const usesHeaderAuth = Boolean(readAuthCookie());
  return {
    plugin: XHRUpload,
    options: {
      endpoint: `${backendUrl}/media/upload-server`,
      withCredentials: !usesHeaderAuth,
      headers: () => {
        const token = readAuthCookie();
        return token ? { auth: decodeURIComponent(token) } : {};
      },
    },
  };
}

In the default cookie-auth mode this is byte-for-byte equivalent to today's behaviour (withCredentials: true, no extra headers), so there is no regression risk for the common path. In NOT_SECURED mode it sends the same auth header every other authenticated request already sends.

Happy to open a PR — I have the change working locally.

Environment

  • postiz 81af6c9
  • STORAGE_PROVIDER=local, NOT_SECURED=true
  • Node 24, pnpm 10.6.1, macOS

Found while using postiz as an independent conformance client against a local API emulator, so the whole stack was on localhost with the frontend and backend on separate ports.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions