Skip to content

Conversation

harryadel
Copy link
Contributor

@harryadel harryadel commented Jun 29, 2025

httpOnly flag is a server side flag, adding it to the client side cookie creation code invalidates it.

It's definitely possible that we can move the token creation to the server-side for more secure tokens but it's necessary to have you green light it first @dr-dimitru

I'm thinking we inject the token somewhere here:

WebApp.connectHandlers.use(async (httpReq, httpResp, next) => {

and we completely remove the need to instantiate the token on the client side. Do you have any objections? Let me know about the best route and I'll gladly implement it.

I'm even more interested as to why you did it this way. Why was it created on the client side then sent to the server. Another peculiar problem is why do you rely on the lastSessionId to validate the request and locate the user. Why not store the usual Meteor local storage token in the cookie and use it later on to retrieve the user. This is one of the most unique was I've ever encountered in Meteor.

Thanks to @fidelsam1992 / @determinds for pointing this out and the time/money. 😁

httpOnly is a serverside flag and adding it to the cookie make ostrio:files not add it
@make-github-pseudonymous-again
Copy link
Contributor

I'm even more interested as to why you did it this way. Why was it created on the client side then sent to the server. Another peculiar problem is why do you rely on the lastSessionId to validate the request and locate the user. Why not store the usual Meteor local storage token in the cookie and use it later on to retrieve the user. This is one of the most unique was I've ever encountered in Meteor.

I am also wondering why this token is needed in the first place? Is it to make it work when the CDN is served from a different domain? Why not just rely on the Meteor session?

@harryadel
Copy link
Contributor Author

@make-github-pseudonymous-again It's a very interesting way. My theory is it's to only allow users who're currently active (in a session) to view the files AND logged in, because a user could be logged in long time ago (Meteor logins last around ~90 days by default). But I'd like to hear from @dr-dimitru

@make-github-pseudonymous-again
Copy link
Contributor

make-github-pseudonymous-again commented Jul 7, 2025

@harryadel After some research, I think I have answers to your questions:

From looking at Meteor's sources, it looks like the login token is stored in window.localStorage. It can only change on active login and logout-other-sessions action:

https://github.com/meteor/meteor/blob/90656b665a57f44af56fe14f37adf76a26138179/packages/accounts-base/accounts_client.js#L423

A new "session" id is generated for each tab, and regenerated on refresh, and stored at Meteor.connect._lastSessionId somewhat independently.

https://github.com/meteor/meteor/blob/90656b665a57f44af56fe14f37adf76a26138179/packages/ddp-client/common/message_processors.js#L41

Why not store the usual Meteor local storage token in the cookie and use it later on to retrieve the user.

The login token or the session id can be used to identify the user. The session id has the advantage of being short-lived and more identifiable, since you can distinguish between different browser tabs.

The login action (whether active, or passive on tab refresh) links the login token to each session here:

https://github.com/meteor/meteor/blob/90656b665a57f44af56fe14f37adf76a26138179/packages/accounts-base/accounts_server.js#L418-L422

Why was it created on the client side then sent to the server.

Since everything in vanilla Meteor happens over DDP (methods and subscriptions over WebSocket), there is no way for the server to drive the creation of the corresponding cookie on the client.

I'm thinking we inject the token somewhere here: and we completely remove the need to instantiate the token on the client side. Do you have any objections? Let me know about the best route and I'll gladly implement it.

You could expose an HTTP endpoint that responds with a set-cookie instruction for one of those, but pay attention to how this HTTP request does not escape the DDP-curse:

httpResp.setHeader('Access-Control-Allow-Headers', 'Range, Content-Type, x-mtok, x-start, x-chunkid, x-fileid, x-eof');

It does not work if you do not pass a way to identify the user first. You could make the endpoint accept the "login token" or "session id" as an authorization header token though.

const setTokenCookie = () => {
if (Meteor.connection._lastSessionId) {
cookie.set('x_mtok', Meteor.connection._lastSessionId, { path: '/', sameSite: 'Lax', secure: true, httpOnly: true });
cookie.set('x_mtok', Meteor.connection._lastSessionId, { path: '/', sameSite: 'Lax', secure: true });

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure this is not just a local testing issue? Are you using localhost? Would the following be more appropriate:

Suggested change
cookie.set('x_mtok', Meteor.connection._lastSessionId, { path: '/', sameSite: 'Lax', secure: true });
cookie.set('x_mtok', Meteor.connection._lastSessionId, { path: '/', sameSite: 'Lax', secure: Meteor.isProduction, httpOnly: Meteor.isProduction });

@make-github-pseudonymous-again
Copy link
Contributor

Since everything in vanilla Meteor happens over DDP (methods and subscriptions over WebSocket), there is no way for the server to drive the creation of the corresponding cookie on the client.

See also https://forums.meteor.com/t/security-dont-store-tokens-in-localstorage/50539.

@make-github-pseudonymous-again
Copy link
Contributor

make-github-pseudonymous-again commented Jul 8, 2025

httpOnly flag is a server side flag, adding it to the client side cookie creation code invalidates it.

More precisely, HttpOnly cookies cannot be created from the client. See:

Admitedly, the current docs at MDN are not crystal clear, but they do say:

"You can also access existing cookies and set new values for them, provided the HttpOnly attribute isn't set on them [...]"

"[...] there are also good reasons why you shouldn't allow JavaScript to modify cookies — i.e., set HttpOnly during creation."

"Cookies that persist user sessions for example should have the HttpOnly attribute set — it would be really insecure to make them available to JavaScript."

Here is a backup of the old docs that are a bit more explicit:

"Cookies created via JavaScript cannot include the HttpOnly flag." - https://udn.realityripple.com/docs/Web/HTTP/Cookies#JavaScript_access_using_Document.cookie

You can actually see the following in the client's browser logs:

Cookie “x_mtok” has been rejected because there is already an HTTP-Only cookie but script tried to store a new one.

and you can easily reproduce that:

>> document.cookie = "foo=bar; HttpOnly";
Cookie “foo” has been rejected because there is already an HTTP-Only cookie but script tried to store a new one.

Essentially, the approach is: "If an HttpOnly cookie cannot be accessed from client code, then it should not be created from client code, so we forbid that.".

@make-github-pseudonymous-again
Copy link
Contributor

make-github-pseudonymous-again commented Jul 8, 2025

You could expose an HTTP endpoint that responds with a set-cookie instruction for one of those

It looks like that feature is already available via ostrio:cookies:

It's just that ostrio:files does not use it for the Web client:

Meteor-Files/client.js

Lines 119 to 121 in 1ed12d1

if ((Meteor.isCordova || Meteor.isDesktop) && this.allowQueryStringCookies) {
cookie.send();
}

@harryadel
Copy link
Contributor Author

Thank you for the deep dive and thoughtful answer @make-github-pseudonymous-again. It's rare that I come across Meteor users this knowledge. I'd love to get in touch. Do you have an email?

I'm still interested to hear from the big dawg @dr-dimitru and possible a new rc till we get to the bottom of this, maybe?

@make-github-pseudonymous-again
Copy link
Contributor

Thank you for the deep dive and thoughtful answer @make-github-pseudonymous-again. It's rare that I come across Meteor users this knowledge. I'd love to get in touch. Do you have an email?

You can reach me at [email protected]

@dr-dimitru
Copy link
Member

@harryadel @make-github-pseudonymous-again thank you for this detailed discussion, pardon me for joining it so much later.

  • Regarding httpOnly and secure flags — Both were added resolving TLS cookie without secure flag set - x_mtok #894 ; httpOnly looks wrong here I agree. I either didn't caught it during testing or browser didn't complain about it back in 2024
  • Regarding lastSessionId — It was used for security and multi-server usage reasons when different tabs may have connected to the different servers; And it was straight forward option to get userId without DB requests right from Meteor.server.sessions

P.S. I'm going to keep secure: Meteor.isProduction and drop httpOnly flags

@dr-dimitru
Copy link
Member

and we completely remove the need to instantiate the token on the client side. Do you have any objections? Let me know about the best route and I'll gladly implement it.

@harryadel do we have sessionId on WebApp/HTTP level here?

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.

3 participants