3

We recently realized that our session cookie is being written out to the fully qualified domain name of our site, www.myapp.com, for example:

MYAPPCOOKIE: 79D5DB83..., domain: www.myapp.com

We want to switch this to being a cookie that can be shared cross subdomain, so any server at myapp.com can use the session cookie as well. For example, we'd like our cookie to store like so:

MYAPPCOOKIE: 79D5DB83..., domain: .myapp.com

We tried just changing our session cookie to use that domain like so:

   Cookie sessionCookie = sessionManager.getSessionIdCookie();
   sessionCookie.setDomain(".myapp.com");                

and this works fine in most cases.

We're finding some users can't login after deploying this new code in some situations. The problem arises when a user:

  • has been logged into our site in their current browser session, but aren't currently logged in.
  • they try and login again

It appears there are 2 session cookies in their browser:

  • a stale cookie from their previous session, with the fully qualified domain name

    MYAPPCOOKIE: 79D5DB83..., domain: www.myapp.com
    
  • the new session cookie for the session they just logged into, with the new domain setting

    MYAPPCOOKIE: 79D5DB83..., domain: .myapp.com
    

What's the best way to manage this old cookie being around? We've tried adding some code to delete the old cookie if a user doesn't have a session, but there are some paths into our app where this doesn't seem to work.

We're open to renaming the cookie if that might work, and are looking for any suggestions others may have. Thanks!

Brad Parks
  • 66,836
  • 64
  • 257
  • 336

2 Answers2

1

The following approach solves the problem by renaming the session cookie to something new, copying the old value to the new cookie name if required.

It uses some Apache modules (mod_headers and mod_setenvif) to copy the old cookie value to the new cookie name when the new cookie doesn't already exist.

  # We only want to copy the old cookie to the new cookie name
  # when it doesnt already exist in the request. To do so, we
  # set a "per request" flag which is used to determine if we need
  # to do the "cookie copy operation".

  <IfModule mod_setenvif.c>
    SetEnvIf Cookie "NEW_SESSION_ID=" HAS_NEW_SESSION_ID
  </IfModule mod_setenvif.c>

  # If the cookie "NEW_SESSION_ID" doesnt exist in the request, copy 
  # the old cookie value to the new cookie name. This allows seamless 
  # switching to the new cookie for users with existing sessions, and
  # also ensure that if we have to rollback our code change for some 
  # reason, the old cookie will still be ok. Of course if new sessions 
  # are created with the new cookie, then they wouldn't be copied over on
  # rollback, but a similar approach could be used if someone 
  # wanted to do so

  <IfModule mod_headers.c>
     RequestHeader edit Cookie "OLD_SESSION_ID=([0-9a-zA-Z\-]*)" "NEW_SESSION_ID=$1 DEBUG_MSG_FOR_REPLACING=TRUE; OLD_SESSION_ID=$1" env=!HAS_NEW_SESSION_ID
  </IfModule mod_headers.c>

Note that you can test if the replace is working by looking for the DEBUG_MSG_FOR_REPLACING cookie value, which I've added for debugging purposes when the replace is done.

The following is some sample code for an endpoint that just dumps the value of the cookies header to the response, which you could use when debugging your Apache changes:

@GET
@Path("dump_cookies")
public Object dumpCookies(@Context HttpServletRequest request)
{
  String result = request.getHeader("Cookie");
  String cookies = result.replace("; ", "<br/>");

  String html = "<h1>Raw</h1>" + result;

  html += "<hr/>";
  html += "<h1>Cookies</h1>" + cookies;

  return html;
}

Note that we didn't end up using this solution because of business reasons that stopped us from renaming the cookie. We ended up using this solution instead which allowed us to keep our cookie name the same.

Community
  • 1
  • 1
Brad Parks
  • 66,836
  • 64
  • 257
  • 336
0

This approach works if you're using Apache Shiro, and deletes the old cookie with the fully qualified domain name before creating the new cookie.

ShiroSessionManager.java

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SessionContext;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;

public class ShiroSessionManager extends DefaultWebSessionManager
{
    @Override
    protected void onStart(Session session, SessionContext context)
    {
        if (isSessionIdCookieEnabled())
        {
            HttpServletRequest request = WebUtils.getHttpRequest(context);
            HttpServletResponse response = WebUtils.getHttpResponse(context);

            removeSessionCookieForFullDomain(request, response);
        }

        super.onStart(session, context);
    }

    private void removeSessionCookieForFullDomain(HttpServletRequest request, HttpServletResponse response)
    {
        Cookie sessionCookie = getSessionIdCookie();
        Cookie cookie = new SimpleCookie(sessionCookie.getName());

        cookie.setSecure(true);
        cookie.setHttpOnly(true);
        cookie.setComment(sessionCookie.getComment());

        // Setting the domain to null means use the fully qualified domain name
        cookie.setDomain(null);

        cookie.setMaxAge(sessionCookie.getMaxAge());
        cookie.setPath(sessionCookie.getPath());
        cookie.setValue(sessionCookie.getValue());
        cookie.setVersion(sessionCookie.getVersion());

        cookie.removeFrom(request, response);
    }
}

To use this, you'd need to set the session manager on your security manager, for example:

    // Create our shiro environment
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    DefaultWebEnvironment environment = new DefaultWebEnvironment();
    DefaultWebSessionManager sessionManager = new ShiroSessionManager();

    // Use the new session manager
    securityManager.setSessionManager(sessionManager);
    environment.setWebSecurityManager(securityManager);
    SecurityUtils.setSecurityManager(securityManager);
Community
  • 1
  • 1
Brad Parks
  • 66,836
  • 64
  • 257
  • 336