Skip to content

critical fix: Image attachments return 403#1231

Open
Aryan-Verma-999 wants to merge 3 commits intoRocketChat:developfrom
Aryan-Verma-999:fix/image-attachment-auth
Open

critical fix: Image attachments return 403#1231
Aryan-Verma-999 wants to merge 3 commits intoRocketChat:developfrom
Aryan-Verma-999:fix/image-attachment-auth

Conversation

@Aryan-Verma-999
Copy link

@Aryan-Verma-999 Aryan-Verma-999 commented Mar 21, 2026

fix: Image attachments return 403 added Rc auth tokens to image URLs

Acceptance Criteria fulfillment

  • Image attachment previews load correctly
  • Auth token is only appended to URLs served from the same RC host
  • Graceful fallback if auth fetch fails (.catch() handled, no unhandled rejection)

Fixes #1229

PR Test Details

Note: The PR will be ready for live testing at https://rocketchat.github.io/EmbeddedChat/pulls/pr-1231 after approval. Contributors are requested to replace <pr_number> with the actual PR number.

@Aryan-Verma-999 Aryan-Verma-999 changed the title fix: Image attachments return 403 critical fix: Image attachments return 403 Mar 21, 2026
@Aryan-Verma-999
Copy link
Author

Aryan-Verma-999 commented Mar 21, 2026

Hey @Spiral-Memory, I pushed the fix for the image preview 403.

While testing, I noticed a separate issue where message history isn’t loading. I’m opening an issue #1232 for that now and looking into a fix.

@Spiral-Memory
Copy link
Collaborator

I didn't get the reason. Why specifically do we need to wrap it in useAuth? Why is it not working? Can you provide an RCA?

@Aryan-Verma-999
Copy link
Author

the actual issue is that image attachments are loaded through /file-upload/... as browser asset requests, not through our normal authenticated API calls. Our API requests work because they send X-Auth-Token and X-User-Id, but the image request itself does not include those headers, so Rocket.Chat treats it as unauthenticated and returns 403.

so the fix is really about making media URLs use the current authenticated session. A hook/shared auth helper is just one way to keep that in sync.

@Spiral-Memory
Copy link
Collaborator

Understood, but the current approach seems risky since it exposes the token in query parameters, and manually verifying the RC host doesn’t feel secure. A better solution might be to use an interceptor pattern, like a service worker or fetch the blob directly in React by including headers and then generate an object URL and use that url to display the place.

@Aryan-Verma-999
Copy link
Author

got it, updating the approach to fetch the blob with auth headers and generate an object URL instead that avoids token exposure in query params entirely. Will push the updated fix shortly

@Aryan-Verma-999
Copy link
Author

Aryan-Verma-999 commented Mar 21, 2026

hey @Spiral-Memory , i need your advice on this
i tried the blob/object URL approach, but it doesn’t work in the current setup.

issue is that protected images are served from /file-upload/..., and when EmbeddedChat is running cross-origin, fetching those files with auth headers triggers a CORS preflight. That route does not seem to allow this flow, so the request fails before we can even create the blob URL.

so the blob approach does not look viable here.

@Aryan-Verma-999
Copy link
Author

Aryan-Verma-999 commented Mar 22, 2026

Hey @Spiral-Memory,
i tried both blob fetch and service worker approach last night but kept bumping into cors preflight.
i went with a BFF proxy instead: after login, auth credentials are stored server-side via /api/proxy-auth and a httpOnly session cookie is set. Image attachments now render as <img src="/api/proxy-media?url=..." /> (no tokens in URLs, no CORS issues.)
Included a Storybook middleware as a dev reference implementation. Let me know what you think of the approach.

@Spiral-Memory
Copy link
Collaborator

Hey, this looks good @Aryan-Verma-999.
Just want to confirm one thing: how can you store credentials on the server side? I mean, does Rocket.Chat allow having some data?

Or are you talking about the API package in EmbeddedChat? (That's not a server; it's part of the EmbeddedChat frontend only.)

@Spiral-Memory
Copy link
Collaborator

Or you're saying, the developers has to provide a proxy url to store it?

@Aryan-Verma-999
Copy link
Author

yeah, since EmbeddedChat is just a frontend widget, it can't store secure cookies itself.
The idea here is that developers using embeddedchat would set up these two simple proxy routes (/api/proxy-auth and /api/proxy-media) on their own backend (like Node, Next.js, or Django) to securely hold the tokens and serve the images.
i just added .storybook/middleware.js as a local dev mock so we can actually test this http-only cookie architecture locally in Storybook without getting blocked by Rocket.Chat's legacy cors rules.

we can just expose a mediaProxyUrl prop in EmbeddedChat so developers can easily point the widget to their own backend proxy. Does this direction sound good to you?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Image attachment previews return 403 Forbidden (uploaded images not visible in chat)

2 participants