I deleted browser’s cache (2 different browsers, 2 different IP’s), then I open my app on browser A, login to my app.
After that I run my web app on browser B, where browser B get the same cookie value as browser A and actually, browser B opens the same site as browser A which should not happen without login.
This does not happen all the time, but sometimes. I can not repeat that all the time and that’s scares me.
Specifically, here are the steps on how to reproduce the problem that is bothering me:
I start Caddy server only with 2 backends and round_robin option (so I can repeat the problem every time). I connect with 3 different devices, from the same IP address. The first two are OK and when I connect with third browser, that third has the same cookie value as one of these two. How to solve this (where each browser have they own cookie value), please?
Are you using Caddy’s sticky backend cookie for authentication…?!
Do not do that! It is not for that purpose, for the very reason you have discovered.
You should have your application set its own cookies on login. Do not rely on the reverse proxy alone for authentication! Handle it in your app, or with an auth provider like Authelia.
To repeat, Caddy’s reverse proxy cookie is NOT for authentication! It is SOLELY to associate a browser with a specific backend. DO NOT use it for authentication. Implement your own cookies for authentication.
Hello and thank you for your support on this.
I’m not using the Caddy cookie for authentication.
I use the WeOnlyDo component (on the background, as a server component) and it sees the third client as the first (if I have 2 backgrounds defined in the Caddyfile) because the third client identifies itself identically to the first. In other words, Caddy (from the third client) sends everything the same as from the first client (if round_robin is set in the configuration).
If I have 2 backgrounds defined in the Caddyfile, doesn’t it make sense for Caddy itself to reset the cookie if it has already distributed to 2 clients and is accessed by a third client?
Of course, I do not mean exclusively the cookie, but the identification of the client as a new one and not as an existing one.
If you only have two backends, how would Caddy invent a third cookie?
Each cookie associates a client to a sticky backend so the same client doesn’t keep making requests to different backends, that’s all it does.
Two backends = two cookies, and each client will get one of these cookies.
It’s the duty of your upstream application to assign an authenticated user a cookie so it can track that user and thereby differentiate them from other users.
You cannot rely on Caddy’s cookie to differentiate clients.
@Whitestrake Thanks for the reply.
Two backgrounds is an example, it can be 2000 backgrounds, but what if 2001 clients come?
I want to explain that it is (IMO) wrong to reuse the same cookie (which was created for client A) and for client C.
I don’t use the Caddy cookie for anything. I have my own user tracking algorithm, but it’s useless: when the third client (I call it “C client”) comes, it’s the same as if the “client A” clicked the refresh button.
I have no way to identify a new client as new.
It’s not wrong to reuse the cookie between clients because Caddy must reuse backends between clients - unless you have more backends than you have clients.
That sounds to me like you’re trying to rely on Caddy’s cookie being different for each client. It isn’t, it’s not designed to be, and it never will be unique to each client; it is only unique to each backend. Caddy is not suitable for differentiating Client A and Client C in your case and you need to implement a different method for authenticating your users.
I do not rely on Caddy’s cookie, I just wanted to say what I see on header and that’s why I think it works like it works.
Can you please tell me is there any other configuration option which distribute each new client to the next backend, it can be round_robin or least_conn.
Important is just that each client (even when he come from same IP address) is unique and once connected, it should be always use the same backend.
Thank you.
Round robin would distribute each request to the next backend in sequence.
There is no way to guarantee that a client from the same IP can’t get the same backend - not even with round robin.
What happens, for example, if Client A makes a request and gets assigned Backend A with round robin, then N more clients are assigned to N more backends, you run out of backends, and then Client C connects to Backend A?
This is a problem you need to solve by introducing more uniqueness - with your own cookies. There is no way for Caddy to ensure uniqueness here.
If we are talking about using the “lb_policy cookie”, then I have no way to use my own cookies because the cookie is assigned when it comes to the server, and even if I delete it, Caddy assigns a cookie with the same value.
I can’t use other options (like round_robin or least_conn) because each JS or CSS is served by a different server and the client can’t get a valid result (I often generate dynamic content).
My idea for using a load balancer is to redirect each client to the server with the fewest connections, keeping all files/requests/responses together.
Cookie option works perfect until it reach max. number of defined backends.
I understand it is related to backend but IMO it is a bug (when using cookie policy and when max. number of backend’s is reached and Caddy send same cookie.
I also understand that it is up to you to define software how to work. I can only ask do you mind to add new “cookie” config with option to reset cookie when number of backend’s is reached.
If not, I’m ready to pay reasonable amount for that.
Is there something preventing you from simply using a different cookie name?
The LB cookie’s job is to keep each client on the backend it started with - it keeps doing this job even after you’ve exhausted unused backends, as it’s intended to. Each cookie associates with one backend - and whichever backend a given client gets, it sticks to.
That’s the designed behaviour, though. I don’t understand what an alterative is supposed to look like.
I don’t understand what this would look like or what purpose it would serve.
Do you mean for Caddy to invalidate all the cookies once you’ve enumerated all the available backends? Just have all your clients shuffle around to newly assigned backends each time you complete an assignment cycle or something? How would Caddy even generate new, different cookies unless you’ve completely switched out the list of backends? What happens when you have more clients connecting at the same time than you have backends to serve them with - do they all just immediately start shuffling around?
I meant this: when Caddy has been distributed cookies to all servers defined in the Caddy file, it should create a new cookie and stick it (based on the fallback setting) to the next server and name it like “definedCookieName_4000”, “definedCookieName_4001” etc.
With other words, Caddy can extend number of cookies and work just like it works now. Is that doable?
No. If you defined 2000 backends here:
reverse_proxy localhost:100 localhost:101 … localhost:2000
and once 2001 client come, then Caddy can create new cookie value (because all existing are already sticked to the 2000 backend’s) and stick it to the next server. Then, Caddy keep connection of this new client to the sticked server, like it do now. Maybe I was not clear in my previous post: cookie name never changes - it should be created new cookie VALUE for each new client.
I’m sorry because of misunderstandings. obviously I did not explained well. Cookie name is always one (defined in the Caddyfile). Changed should be only value for each new client. With other words, each Client have they unique cookie connected with the server.
It was wrong, I’m sorry about that.
This will help all people who use sticky cookie sessions and develop web applications rather than normal websites. Currently, Caddy distributes (in all cases except the cookie setting) all files to all backends, using configuration logic. This is not an option for webapp because some JS or CSS files are used only in part of a dialog or some other component and all files must be received on same backend.
If I don’t use the sticky cookie option, I have no way to identify each new client because all requests from the client to the server are identified as different clients. In other words, each JS or CSS or AJAX call is distributed to a different backend. Only with the cookie setting, requests from the same client are distributed to the same backend and that’s great until the moment there are no more available backends. Then Caddy uses a cookie VALUE that is already in use to distribute the request of the new client and this is unacceptable. I have no way to identify a new client as new, I cannot give it a new ID because it is alredy identified as an existing one. The component I use for each new client creates a unique sessionID (in the UserConnected event). The problem is that a totally new client is identified as an existing one because Caddy sent the details (cookie value) of the existing client. So I can’t get to the client before Caddy but only after and then I have that existing cookie value. You said many times that I can give each client my own sessionID - it is not possible when using Caddy because Caddy is between a client and backend and I can get values only from Caddy. And Caddy send me same cookie value for new client. How I can know that’s new client? I hope it’s clearer now.
That’s why each client should have a unique cookie value (not a name, sorry again). So, the name of the cookie never changes, but the value of the cookie for each new client.
The load balancer cookie value is not designed for each client. The value is unique per upstream server. When using round_robin fallback:
Client1 gets cookie value localhost:100.
Client2 gets cookie value localhost:101.
ClientN gets cookie value localhost:99+N.
Client2001 gets cookie value localhost:101. (It wraps around to the first upstream.)
The cookie is not unique per client, Caddy uses it to figure out which backend to route the request to, that’s all. Caddy would need some kind of scheme to produce unique cookie values… Right now they’re literally just a hash of the upstream address. So Caddy would need to figure out what alternative values to use - and then it would need to figure out how to READ them coming back when they’re, by necessity, unique to each and every client. This is straight up a hijack of the purpose of the load balancing cookie which would make its job far more complicated.
This just sounds like you want to use Caddy’s load balancer cookie for authentication (that is, you want to use it to identify your clients individually, separate from each other).
This is not what the load balancer cookie is for.
Just to clarify - Caddy distributes requests to backends following the logic, yes. The files are up to the backend to provide.
I’ve seen a lot of web apps which can serve static assets (JS, CSS, etc) regardless of which backend those requests were routed to. The static assets are simply uniquely named and uniquely referenced in the contexts in which they’re required, and sometimes versioned as well for cacheing purposes. I’m not sure why this strategy isn’t feasible in your case, but that’s okay.
As I see it you’ve described one problem here: any given client must make all its requests to the same backend. The load balancer cookie solves that by giving the client a cookie that tells Caddy which backend to group all its requests to. That is the sole purpose of the load balancer cookie.
Identifying each client is a completely different problem from the “all requests from the same client have to go to the same backend” problem. This problem is called authentication.
And I don’t understand why you can’t just set your own cookie for each new client. It’s very basic logic that needs be implemented:
If no client-identifier-cookie, return header Set-Cookie: client-identifier-cookie=<some SUFFICIENTLY RANDOM hash>.
Then use client-identifier-cookie. This is a very standard solution. Why is this infeasible in your case? Why is it better to change Caddy to do this for you when you can simply do it yourself? Even better - it makes you reverse-proxy-agnostic, which means you’re not REQUIRED to include Caddy in your technology stack for your application.
When there’s no available backends, Caddy loops back to the first backend. That is indeed how the cookie works.
This isn’t a problem unless you’re trying to use the cookie for authentication (i.e. uniquely identifying and differentiating individual clients). The answer to this problem is simple: Don’t rely on the lb cookie for that purpose. Set your own cookie.
Don’t rely on Caddy’s lb cookie to identify clients. It does not identify clients, it identifies backends. The value of the cookie is just the hash of one of the backends. For every client you get, you need to set your own client cookie if it doesn’t already have one. This needs to be part of your authentication process.
Why is your component relying on Caddy’s load balancer cookie to uniquely identify clients? It needs to NOT do this. That cookie can’t be relied on for this purpose whatsoever.
Modifying the load balancer cookie to make it useful for authentication will make it overcomplicated and less useful for its actual intended purpose, which is strictly for load balancing.
If it was Caddy’s job to solve session authentication, it would be better simply to propose an entirely new per-client cookie, completely irrespective of the load balancing process. But it’s not Caddy’s job to solve - it’s your webapp, which needs to authenticate users and can do so easily by setting browser session variables or setting its own cookie instead of trying to read Caddy’s load balancing cookie and relying on that.
Ignore the cookie value when you get it. Pretend the load balancer cookie doesn’t exist. It’s not useful to your webapp.
For every request, if client-identifier-cookie isn’t sent, set it. Then rely on that to identify each session moving forwards.
The way you know it’s a new client is that it doesn’t have client-identifier-cookie (or whatever you want to call your own cookie). So - validate the request, return a Set-Cookie header, and use that. Pretend Caddy isn’t there and rely on no Caddy-specific headers. Just have your webapp talk to the client as though it was talking directly to the client. Need a cookie? Set a cookie you can rely on.
@Whitestrake Thank you so much for your time and patience. Now (finally) I understand what you meant. I added my own cookie to the index.html page and it works perfectly with any option, including round_robin.
I just didn’t realize it was that easy and I apologize for bothered you and anyone else. Life is great!