ServiceStack cookies and Path

Hi, my ServiceStack app has 3 “sections” with separate sessions.

I have the need to use 3 different cookies, that differs only by path. The login procedure should generate a cookie with the respective Path.

Example:
https://my.app.com/mainSection/login
Cookie Domain: app.com
Cookie Path: /mainSection

https://my.app.com/otherSection/login
Cookie Domain: app.com
Cookie Path: /otherSection

How can I configure ServiceStack to do that?

You can return your own Cookies with:

return new HttpResult(responseDto)
{
    Cookies = {
        new Cookie(name, value, path) {
            HttpOnly = true,
            Secure = Request.IsSecureConnection,
            Expires = DateTime.UtcNow.Add(jwtAuthProvider.ExpireTokensIn),
        }
    }
};

But you couldn’t change the built-in Cookies for ServiceStack Sessions only if they should be returned by overriding AppHost.AllowSetCookie(). I’ve just added AppHost.SetCookieFilter() in this commit. This change is available from v5.1.1 that’s now available on MyGet.

The SetCookieFilter seems to be the solutions we are looking for, but it does not seem to work on the ss-id cookie:

All other cookies are fine, but ss-id does not get the new Path, although I can see that SetCookieFilter is getting called for that cookie.

The ss-id cookie is the cookie we need this feature the most.

You may have existing cookies or the path doesn’t match the request as overriding SetCookieFilter is setting the cookie path:

public override bool SetCookieFilter(IRequest req, Cookie cookie)
{
    cookie.Path = "/hello";   
    return base.SetCookieFilter(req, cookie);
}

Edit: Found that! The following line is what is causing my problem. Why is it like that? You don’t have that error because you are using IIS, while I’m using the self-host version.


This is my result:

I’m using a custom CredentialsAuthProvider. Do you have any clue on why only that cookie works like that?

This is my override for SetCookieFilter

public override bool SetCookieFilter(IRequest req, Cookie cookie)
{
    string module = GetModuleName(req);

    cookie.Path = string.Format("/{0}", module ?? "");
    Console.WriteLine(string.Format("{0} '{1}'", cookie.Name, cookie.Path);

    return base.SetCookieFilter(req, cookie);
}

This is an example output of that Console.WriteLine:

I’ve tested with Firefox and Insomnia, with and without old cookies (and Incognito mode).

Not sure the logic of a / for session cookies has basically been there since the start and its behavior retained through multiple refactoring. From what I can tell from the earliest source code it was due to SetSessionCookie(name,value) only having 2 arguments to maintain a simple API, so I’m assuming it’s not necessary as the other hosts don’t have this behavior.

I’ve changed it to use cookie.Path if it exists in this commit, this change is available from the latest v5.1.1 that’s now available on MyGet.

It works fine, but the ServerEvents paths seems not to understand the “{ignore}” placeholder. Is it possible to implement the same routing policy of the other paths?

var serverEvents = new ServerEventsFeature
            {
                LimitToAuthenticatedUsers = true,

                IdleTimeout = TimeSpan.FromSeconds(90),

                HeartbeatInterval = TimeSpan.FromSeconds(30),

                NotifyChannelOfSubscriptions = false,
            };

            serverEvents.HeartbeatPath = $$"/{{ignore}}/{serverEvents.HeartbeatPath}";
            serverEvents.StreamPath = $$"/{{ignore}}/{serverEvents.StreamPath}";
            serverEvents.SubscribersPath = $$"/{{ignore}}/{serverEvents.SubscribersPath}";
            serverEvents.UnRegisterPath = $$"/{{ignore}}/{serverEvents.UnRegisterPath}";

No it’s not a Route, it’s an path endpoint.

No it’s not possible, it needs to be a single static path. The SSE event-stream is not a Service, it’s a long-running HTTP Handler, they’re used to generate URLs which are sent to the client which use them verbatim.

There can only be a single path, if you want to pass custom params pass them on the queryString.

Querystring is not an options since we are talking about Cookie Path.

Anyway, I’d set up a ProxyFeature that basically does what {ignore} does, if anyone need this:

Plugins.Add(new ProxyFeature(
                req => Regex.IsMatch(req.PathInfo, @"^(\/[^\/]*)*?\/event-.*$"),
                req => Regex.Replace(req.PathInfo, @"^\/[^\/]*", "")
            ));