You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by en...@apache.org on 2021/05/26 18:31:46 UTC

[sling-org-apache-sling-auth-form] branch master updated: SLING-10421 validate configured and client supplied cookie domain value (#4)

This is an automated email from the ASF dual-hosted git repository.

enorman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-auth-form.git


The following commit(s) were added to refs/heads/master by this push:
     new f06f71f  SLING-10421 validate configured and client supplied cookie domain value (#4)
f06f71f is described below

commit f06f71fcb4e1100375f8adbeb3d5e6e0a5c755ee
Author: Eric Norman <er...@gmail.com>
AuthorDate: Wed May 26 11:31:32 2021 -0700

    SLING-10421 validate configured and client supplied cookie domain value (#4)
    
    also add integration tests to cover the code changes
---
 .../auth/form/impl/FormAuthenticationHandler.java  |  75 ++++--
 ...10290IT.java => AuthFormClientTestSupport.java} | 153 ++++--------
 .../apache/sling/auth/form/it/SLING10290IT.java    | 273 +--------------------
 .../auth/form/it/SLING10421InvalidDomainIT.java    | 141 +++++++++++
 .../auth/form/it/SLING10421ValidDomainIT.java      | 147 +++++++++++
 5 files changed, 387 insertions(+), 402 deletions(-)

diff --git a/src/main/java/org/apache/sling/auth/form/impl/FormAuthenticationHandler.java b/src/main/java/org/apache/sling/auth/form/impl/FormAuthenticationHandler.java
index bbfc8df..8ee2cd2 100644
--- a/src/main/java/org/apache/sling/auth/form/impl/FormAuthenticationHandler.java
+++ b/src/main/java/org/apache/sling/auth/form/impl/FormAuthenticationHandler.java
@@ -728,14 +728,7 @@ public class FormAuthenticationHandler extends DefaultAuthenticationFeedbackHand
      * in an HTTP Cookie.
      */
     private static class CookieStorage implements AuthenticationStorage {
-
-        /**
-         * The Set-Cookie header used to manage the login cookie.
-         *
-         * @see CookieStorage#setCookie(HttpServletRequest, HttpServletResponse, String,
-         *      String, int, String)
-         */
-        private static final String HEADER_SET_COOKIE = "Set-Cookie";
+        private final Logger log = LoggerFactory.getLogger(getClass());
 
         private final String cookieName;
         private final String domainCookieName;
@@ -777,6 +770,12 @@ public class FormAuthenticationHandler extends DefaultAuthenticationFeedbackHand
             if (cookieDomain == null || cookieDomain.length() == 0) {
                 cookieDomain = defaultCookieDomain;
             }
+
+            if (!isValidCookieDomain(request, cookieDomain)) {
+                log.warn("Sending formauth cookies without a cookie domain because the configured value is invalid for the request");
+                cookieDomain = null;
+            }
+
             setCookie(request, response, this.cookieName, cookieValue, -1, cookieDomain);
 
             // send the cookie domain cookie if domain is not null
@@ -797,10 +796,23 @@ public class FormAuthenticationHandler extends DefaultAuthenticationFeedbackHand
                         oldCookie = cookie;
                     } else if (this.domainCookieName.equals(cookie.getName())) {
                         oldCookieDomain = cookie.getValue();
+                        if (oldCookieDomain.length() == 0) {
+                            oldCookieDomain = null;
+                        }
                     }
                 }
             }
 
+            if (!isValidCookieDomain(request, oldCookieDomain)) {
+                if (!isValidCookieDomain(request, defaultCookieDomain)) {
+                    log.warn("The client supplied domain cookie value was invalid and the configured default cookie domain is also invalid. Will try clearing the cookies without a domain instead");
+                    oldCookieDomain = null;
+                } else {
+                    log.warn("The client supplied domain cookie value was invalid. Will try clearing the cookies with the default cookie domain instead");
+                    oldCookieDomain = defaultCookieDomain;
+                }
+            }
+
             // remove the old cookie from the client
             if (oldCookie != null) {
                 setCookie(request, response, this.cookieName, "", 0, oldCookieDomain);
@@ -810,42 +822,51 @@ public class FormAuthenticationHandler extends DefaultAuthenticationFeedbackHand
             }
         }
 
+        /**
+         * Validates that the cookie domain is valid for the request host
+         * 
+         * @param request the current request
+         * @param cookieDomain the candidate cookie domain value
+         * @return true if valid, false otherwise
+         */
+        private boolean isValidCookieDomain(HttpServletRequest request, String cookieDomain) {
+            boolean valid = false;
+            if (cookieDomain == null) {
+                valid = true;
+            } else {
+                // a valid cookie domain must be a suffix of the host
+                String host = request.getServerName();
+                if (host.endsWith(cookieDomain)) {
+                    valid = true;
+                }
+            }
+            return valid;
+        }
+
         private void setCookie(final HttpServletRequest request, final HttpServletResponse response, final String name,
                 final String value, final int age, final String domain) {
 
             final String ctxPath = request.getContextPath();
             final String cookiePath = (ctxPath == null || ctxPath.length() == 0) ? "/" : ctxPath;
 
-            /*
-             * The Servlet Spec 2.5 does not allow us to set the commonly used HttpOnly
-             * attribute on cookies (Servlet API 3.0 does) so we create the Set-Cookie
-             * header manually. See http://www.owasp.org/index.php/HttpOnly for information
-             * on what the HttpOnly attribute is used for.
-             */
-
-            final StringBuilder header = new StringBuilder();
-
-            // default setup with name, value, cookie path and HttpOnly
-            header.append(name).append("=").append(value);
-            header.append("; Path=").append(cookiePath);
-            header.append("; HttpOnly"); // don't allow JS access
+            Cookie c = new Cookie(name, value);
+            c.setPath(cookiePath);
+            c.setHttpOnly(true); // don't allow JS access
 
             // set the cookie domain if so configured
             if (domain != null) {
-                header.append("; Domain=").append(domain);
+                c.setDomain(domain);
             }
 
             // Only set the Max-Age attribute to remove the cookie
             if (age >= 0) {
-                header.append("; Max-Age=").append(age);
+                c.setMaxAge(age);
             }
 
             // ensure the cookie is secured if this is an https request
-            if (request.isSecure()) {
-                header.append("; Secure");
-            }
+            c.setSecure(request.isSecure());
 
-            response.addHeader(HEADER_SET_COOKIE, header.toString());
+            response.addCookie(c);
         }
     }
 
diff --git a/src/test/java/org/apache/sling/auth/form/it/SLING10290IT.java b/src/test/java/org/apache/sling/auth/form/it/AuthFormClientTestSupport.java
similarity index 68%
copy from src/test/java/org/apache/sling/auth/form/it/SLING10290IT.java
copy to src/test/java/org/apache/sling/auth/form/it/AuthFormClientTestSupport.java
index 8b16392..56ae667 100644
--- a/src/test/java/org/apache/sling/auth/form/it/SLING10290IT.java
+++ b/src/test/java/org/apache/sling/auth/form/it/AuthFormClientTestSupport.java
@@ -25,7 +25,6 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.factoryConfiguration;
-import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
 
 import java.io.IOException;
 import java.net.URI;
@@ -34,7 +33,6 @@ import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Date;
 import java.util.Dictionary;
 import java.util.List;
 
@@ -63,31 +61,25 @@ import org.apache.http.message.BasicNameValuePair;
 import org.apache.http.util.EntityUtils;
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
 import org.ops4j.pax.exam.Option;
-import org.ops4j.pax.exam.junit.PaxExam;
-import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
-import org.ops4j.pax.exam.spi.reactors.PerClass;
 import org.osgi.service.cm.Configuration;
 import org.osgi.service.cm.ConfigurationAdmin;
 
 /**
- * integration tests to verify fix for SLING-10290
+ * base class for tests doing http requests to verify forms auth
  */
-@RunWith(PaxExam.class)
-@ExamReactorStrategy(PerClass.class)
-public class SLING10290IT extends AuthFormTestSupport {
+public abstract class AuthFormClientTestSupport extends AuthFormTestSupport {
 
     @Inject
-    private ConfigurationAdmin cm;
+    protected ConfigurationAdmin cm;
 
-    private static final String COOKIE_SLING_FORMAUTH = "sling.formauth";
-    private static final String HEADER_SET_COOKIE = "Set-Cookie";
+    protected static final String COOKIE_SLING_FORMAUTH = "sling.formauth";
+    protected static final String COOKIE_SLING_FORMAUTH_DOMAIN = "sling.formauth.cookie.domain";
+    protected static final String HEADER_SET_COOKIE = "Set-Cookie";
 
-    private URI baseServerUri;
-    private HttpClientContext httpContext;
-    private CloseableHttpClient httpClient;
+    protected URI baseServerUri;
+    protected HttpClientContext httpContext;
+    protected CloseableHttpClient httpClient;
 
     @Override
     protected Option[] additionalOptions() throws IOException {
@@ -103,11 +95,7 @@ public class SLING10290IT extends AuthFormTestSupport {
             // add the test script tinybundle
             bundle,
 
-            // change the formauth timeout to 1 minute so we don't have to wait a long
-            //   time for the testRefreshCookieOnRequestAfterHalfExpirationDuration test
-            newConfiguration("org.apache.sling.auth.form.FormAuthenticationHandler")
-                .put("form.auth.timeout", "1")
-            .asOption(),
+            newFormauthHandlerConfiguration(),
 
             // enable the healthcheck configuration for checking when the server is ready to
             //  receive http requests.  (adapted from the starter healthcheck.json configuration)
@@ -134,6 +122,8 @@ public class SLING10290IT extends AuthFormTestSupport {
         };
     }
 
+    protected abstract Option newFormauthHandlerConfiguration();
+
     @Before
     public void before() throws IOException, URISyntaxException {
         // wait for the health checks to be OK
@@ -166,91 +156,11 @@ public class SLING10290IT extends AuthFormTestSupport {
         baseServerUri = null;
     }
 
-    @Test
-    public void testLoginFormRenders() throws IOException {
-        HttpGet loginformRequest = new HttpGet(String.format("%s/system/sling/form/login", baseServerUri));
-        try (CloseableHttpResponse loginformResponse = httpClient.execute(loginformRequest, httpContext)) {
-            assertEquals(HttpServletResponse.SC_OK, loginformResponse.getStatusLine().getStatusCode());
-            String content = EntityUtils.toString(loginformResponse.getEntity());
-            assertTrue(content.contains("Login to Apache Sling"));
-            assertTrue(content.contains("loginform"));
-        }
-    }
-
-    @Test
-    public void testLogout() throws IOException, MalformedCookieException {
-        doFormsLogin();
-        HttpGet logoutRequest = new HttpGet(String.format("%s/system/sling/logout", baseServerUri));
-        try (CloseableHttpResponse logoutResponse = httpClient.execute(logoutRequest, httpContext)) {
-            assertEquals(HttpServletResponse.SC_MOVED_TEMPORARILY, logoutResponse.getStatusLine().getStatusCode());
-            Cookie parsedFormauthCookie = parseFormAuthCookieFromHeaders(logoutResponse);
-            assertNotNull("Expected a formauth cookie in the response", parsedFormauthCookie);
-            assertEquals("Expected the formauth cookie value to be empty", "", parsedFormauthCookie.getValue());
-            assertTrue("Expected the formauth cookie to be expired", parsedFormauthCookie.isExpired(new Date()));
-            Cookie formauthCookie2 = getFormAuthCookieFromCookieStore();
-            assertNull("Did not expected a formauth cookie in the cookie store", formauthCookie2);
-        }
-    }
-
-    /**
-     * Verify that the formauth cookie is sent appropriately after login
-     */
-    @Test
-    public void testSetCookieOnFirstRequestAfterLogin() throws MalformedCookieException, IOException {
-        doFormsLogin();
-    }
-
-    /**
-     * Verify that the formauth cookie is not re-sent on each request after login
-     */
-    @Test
-    public void testNoSetCookieOnSecondRequestAfterLogin() throws MalformedCookieException, IOException {
-        // 1. login as the test user
-        doFormsLogin();
-
-        // 2. do another request
-        HttpGet request = new HttpGet(whoamiUri());
-        try (CloseableHttpResponse response = httpClient.execute(request, httpContext)) {
-            assertEquals(HttpServletResponse.SC_OK, response.getStatusLine().getStatusCode());
-            Cookie parsedFormauthCookie = parseFormAuthCookieFromHeaders(response);
-            assertNull("Did not expect a formauth cookie in the response", parsedFormauthCookie);
-        }
-    }
-
-    /**
-     * Verify that the formauth cookie is refreshed on the first request after half the session duration
-     * has occurred
-     */
-    @Test
-    public void testRefreshCookieOnRequestAfterHalfExpirationDuration() throws InterruptedException, MalformedCookieException, IOException {
-        // 1. login as the test user
-        doFormsLogin();
-
-        // 2. wait for half the session timeout expiration duration
-        Thread.sleep((Duration.ofMinutes(1).toMillis() / 2) + 1); // NOSONAR
-
-        // 3. do another request to trigger the cookie refresh
-        HttpGet request = new HttpGet(whoamiUri());
-        try (CloseableHttpResponse response = httpClient.execute(request, httpContext)) {
-            assertEquals(HttpServletResponse.SC_OK, response.getStatusLine().getStatusCode());
-            Cookie parsedFormauthCookie = parseFormAuthCookieFromHeaders(response);
-            assertNotNull("Expected a refreshed formauth cookie in the response", parsedFormauthCookie);
-        }
-
-        // 4. do another request to verify that subsequent request after
-        //    the cookie refresh do not send an additional formauth cookie
-        try (CloseableHttpResponse response = httpClient.execute(request, httpContext)) {
-            assertEquals(HttpServletResponse.SC_OK, response.getStatusLine().getStatusCode());
-            Cookie parsedFormauthCookie2 = parseFormAuthCookieFromHeaders(response);
-            assertNull("Did not expect a formauth cookie in the response", parsedFormauthCookie2);
-        }
-    }
-
     /**
      * Calculate the base server URI from the current configuration of the
      * httpservice
      */
-    private URI getBaseServerUri() throws IOException, URISyntaxException {
+    protected URI getBaseServerUri() throws IOException, URISyntaxException {
         assertNotNull(cm);
         Configuration httpServiceConfiguration = cm.getConfiguration("org.apache.felix.http");
         Dictionary<String, Object> properties = httpServiceConfiguration.getProperties();
@@ -292,14 +202,17 @@ public class SLING10290IT extends AuthFormTestSupport {
     /**
      * @return the address of the whoami script
      */
-    private String whoamiUri() {
+    protected String whoamiUri() {
         return String.format("%s/content.SLING10290IT.html", baseServerUri);
     }
 
     /**
      * Perform the http calls to login the test user via the forms based login
      */
-    private void doFormsLogin() throws MalformedCookieException, IOException {
+    protected void doFormsLogin() throws MalformedCookieException, IOException {
+        doFormsLogin(null, null);
+    }
+    protected void doFormsLogin(ValidateFormauthCookie formauthCookieValidator, ValidateFormauthDomainCookie domainCookieValidator) throws MalformedCookieException, IOException {
         // before login, there should be no formauth cookie in the cookie store
         Cookie formauthCookie = getFormAuthCookieFromCookieStore();
         assertNull("Did not expect formauth cookie in the cookie store", formauthCookie);
@@ -329,6 +242,14 @@ public class SLING10290IT extends AuthFormTestSupport {
             // verify that the expected set-cookie header arrived
             Cookie parsedFormauthCookie = parseFormAuthCookieFromHeaders(response);
             assertNotNull("Expected a formauth cookie in the response", parsedFormauthCookie);
+            
+            if (formauthCookieValidator != null) {
+                formauthCookieValidator.validate(parsedFormauthCookie);
+            }
+            if (domainCookieValidator != null) {
+                Cookie parsedDomainCookie = parseCookieFromHeaders(response, COOKIE_SLING_FORMAUTH_DOMAIN);
+                domainCookieValidator.validate(parsedDomainCookie);
+            }
         }
 
         // after login, there should be now be a cookie in the cookie store
@@ -356,12 +277,15 @@ public class SLING10290IT extends AuthFormTestSupport {
      * 
      * @return the formauth cookie or null if not found
      */
-    private Cookie getFormAuthCookieFromCookieStore() {
+    protected Cookie getFormAuthCookieFromCookieStore() {
+        return getCookieFromCookieStore(COOKIE_SLING_FORMAUTH);
+    }
+    protected Cookie getCookieFromCookieStore(String cookieName) {
         Cookie formauthCookie = null;
         List<Cookie> cookies = httpContext.getCookieStore().getCookies();
         if (cookies != null) {
             for (Cookie c : cookies) {
-                if (COOKIE_SLING_FORMAUTH.equals(c.getName())) {
+                if (cookieName.equals(c.getName())) {
                     formauthCookie = c;
                 }
             }
@@ -375,7 +299,10 @@ public class SLING10290IT extends AuthFormTestSupport {
      * @param response the response from the http request
      * @return the found cookie or null if not found
      */
-    private Cookie parseFormAuthCookieFromHeaders(HttpResponse response) throws MalformedCookieException {
+    protected Cookie parseFormAuthCookieFromHeaders(HttpResponse response) throws MalformedCookieException {
+        return parseCookieFromHeaders(response, COOKIE_SLING_FORMAUTH);
+    }
+    protected Cookie parseCookieFromHeaders(HttpResponse response, String cookieName) throws MalformedCookieException {
         Header [] cookieHeaders = response.getHeaders(HEADER_SET_COOKIE);
         assertNotNull(cookieHeaders);
 
@@ -386,7 +313,7 @@ public class SLING10290IT extends AuthFormTestSupport {
         for (Header cookieHeader : cookieHeaders) {
             List<Cookie> parsedCookies = cookieSpec.parse(cookieHeader, origin);
             for (Cookie c : parsedCookies) {
-                if (COOKIE_SLING_FORMAUTH.equals(c.getName())) {
+                if (cookieName.equals(c.getName())) {
                     if (parsedFormauthCookie != null) {
                         fail(String.format("Did not expect more than one %s cookie", c.getName()));
                     }
@@ -397,4 +324,12 @@ public class SLING10290IT extends AuthFormTestSupport {
         return parsedFormauthCookie;
     }
 
+    protected static interface ValidateFormauthCookie {
+        void validate(Cookie cookie);
+    }
+
+    protected static interface ValidateFormauthDomainCookie {
+        void validate(Cookie cookie);
+    }
+
 }
diff --git a/src/test/java/org/apache/sling/auth/form/it/SLING10290IT.java b/src/test/java/org/apache/sling/auth/form/it/SLING10290IT.java
index 8b16392..144f14a 100644
--- a/src/test/java/org/apache/sling/auth/form/it/SLING10290IT.java
+++ b/src/test/java/org/apache/sling/auth/form/it/SLING10290IT.java
@@ -18,152 +18,44 @@
  */
 package org.apache.sling.auth.form.it;
 
-import static org.apache.sling.testing.paxexam.SlingOptions.slingScriptingSightly;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.factoryConfiguration;
 import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
 
 import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
 import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Date;
-import java.util.Dictionary;
-import java.util.List;
 
-import javax.inject.Inject;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.http.Header;
-import org.apache.http.HttpResponse;
-import org.apache.http.NameValuePair;
-import org.apache.http.client.config.CookieSpecs;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.entity.UrlEncodedFormEntity;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.protocol.HttpClientContext;
 import org.apache.http.cookie.Cookie;
-import org.apache.http.cookie.CookieOrigin;
-import org.apache.http.cookie.CookieSpec;
 import org.apache.http.cookie.MalformedCookieException;
-import org.apache.http.impl.client.BasicCookieStore;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
-import org.apache.http.impl.cookie.RFC6265StrictSpec;
-import org.apache.http.message.BasicNameValuePair;
 import org.apache.http.util.EntityUtils;
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.ops4j.pax.exam.Option;
 import org.ops4j.pax.exam.junit.PaxExam;
 import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
 import org.ops4j.pax.exam.spi.reactors.PerClass;
-import org.osgi.service.cm.Configuration;
-import org.osgi.service.cm.ConfigurationAdmin;
 
 /**
  * integration tests to verify fix for SLING-10290
  */
 @RunWith(PaxExam.class)
 @ExamReactorStrategy(PerClass.class)
-public class SLING10290IT extends AuthFormTestSupport {
-
-    @Inject
-    private ConfigurationAdmin cm;
-
-    private static final String COOKIE_SLING_FORMAUTH = "sling.formauth";
-    private static final String HEADER_SET_COOKIE = "Set-Cookie";
-
-    private URI baseServerUri;
-    private HttpClientContext httpContext;
-    private CloseableHttpClient httpClient;
+public class SLING10290IT extends AuthFormClientTestSupport {
 
     @Override
-    protected Option[] additionalOptions() throws IOException {
-        // create a tinybundle that contains a test script
-        final List<String> resourcePaths = Arrays.asList("/apps/sling/OrderedFolder/SLING10290IT.html");
-        final String bundleResourcesHeader = String.join(",", resourcePaths);
-        final Option bundle = buildBundleResourcesBundle(bundleResourcesHeader, resourcePaths);
-
-        return new Option[]{
-            // add sightly support for the test script
-            slingScriptingSightly(),
-
-            // add the test script tinybundle
-            bundle,
-
-            // change the formauth timeout to 1 minute so we don't have to wait a long
-            //   time for the testRefreshCookieOnRequestAfterHalfExpirationDuration test
-            newConfiguration("org.apache.sling.auth.form.FormAuthenticationHandler")
-                .put("form.auth.timeout", "1")
-            .asOption(),
-
-            // enable the healthcheck configuration for checking when the server is ready to
-            //  receive http requests.  (adapted from the starter healthcheck.json configuration)
-            factoryConfiguration("org.apache.felix.hc.generalchecks.FrameworkStartCheck")
-                .put("hc.tags", new String[] {"systemalive"})
-                .put("targetStartLevel", 5)
-                .asOption(),
-            factoryConfiguration("org.apache.felix.hc.generalchecks.ServicesCheck")
-                .put("hc.tags", new String[] {"systemalive"})
-                .put("services.list", new String[] {
-                        "org.apache.sling.jcr.api.SlingRepository",
-                        "org.apache.sling.engine.auth.Authenticator",
-                        "org.apache.sling.api.resource.ResourceResolverFactory",
-                        "org.apache.sling.api.servlets.ServletResolver",
-                        "javax.script.ScriptEngineManager"
-                })
-                .asOption(),
-            factoryConfiguration("org.apache.felix.hc.generalchecks.BundlesStartedCheck")
-                .put("hc.tags", new String[] {"bundles"})
-                .asOption(),
-            factoryConfiguration("org.apache.sling.jcr.contentloader.hc.BundleContentLoadedCheck")
-                .put("hc.tags", new String[] {"bundles"})
-                .asOption(),
-        };
-    }
-
-    @Before
-    public void before() throws IOException, URISyntaxException {
-        // wait for the health checks to be OK
-        waitForServerReady(Duration.ofMinutes(1).toMillis(), 500);
-
-        // calculate the address of the http server
-        baseServerUri = getBaseServerUri();
-        assertNotNull(baseServerUri);
-
-        // prepare the http client for the test user
-        httpContext = HttpClientContext.create();
-        httpContext.setCookieStore(new BasicCookieStore());
-        RequestConfig requestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD_STRICT).build();
-        httpContext.setRequestConfig(requestConfig);
-        httpClient = HttpClients.custom()
-                .disableRedirectHandling()
-                .build();
-    }
-
-    @After
-    public void after() throws IOException {
-        // close/cleanup the test user http client
-        if (httpClient != null) {
-            httpClient.close();
-            httpClient = null;
-        }
-
-        // clear out other state
-        httpContext = null;
-        baseServerUri = null;
+    protected Option newFormauthHandlerConfiguration() {
+        // change the formauth timeout to 1 minute so we don't have to wait a long
+        //   time for the testRefreshCookieOnRequestAfterHalfExpirationDuration test
+        return newConfiguration("org.apache.sling.auth.form.FormAuthenticationHandler")
+            .put("form.auth.timeout", "1")
+        .asOption();
     }
 
     @Test
@@ -246,155 +138,4 @@ public class SLING10290IT extends AuthFormTestSupport {
         }
     }
 
-    /**
-     * Calculate the base server URI from the current configuration of the
-     * httpservice
-     */
-    private URI getBaseServerUri() throws IOException, URISyntaxException {
-        assertNotNull(cm);
-        Configuration httpServiceConfiguration = cm.getConfiguration("org.apache.felix.http");
-        Dictionary<String, Object> properties = httpServiceConfiguration.getProperties();
-
-        String host;
-        Object hostObj = properties.get("org.apache.felix.http.host");
-        if (hostObj == null) {
-            host = "localhost";
-        } else {
-            assertTrue(hostObj instanceof String);
-            host = (String)hostObj;
-        }
-        assertNotNull(host);
-
-        String scheme = null;
-        Object portObj = null;
-        Object httpsEnableObj = properties.get("org.apache.felix.https.enable");
-        if ("true".equals(httpsEnableObj)) {
-            scheme = "https";
-            portObj = properties.get("org.osgi.service.http.port.secure");
-        } else {
-            Object httpEnableObj = properties.get("org.apache.felix.http.enable");
-            if (httpEnableObj == null || "true".equals(httpEnableObj)) {
-                scheme = "http";
-                portObj = properties.get("org.osgi.service.http.port");
-            } else {
-                fail("Expected either http or https to be enabled");
-            }
-        }
-        int port = -1;
-        if (portObj instanceof Number) {
-            port = ((Number)portObj).intValue();
-        }
-        assertTrue(port > 0);
-
-        return new URI(String.format("%s://%s:%d", scheme, host, port));
-    }
-
-    /**
-     * @return the address of the whoami script
-     */
-    private String whoamiUri() {
-        return String.format("%s/content.SLING10290IT.html", baseServerUri);
-    }
-
-    /**
-     * Perform the http calls to login the test user via the forms based login
-     */
-    private void doFormsLogin() throws MalformedCookieException, IOException {
-        // before login, there should be no formauth cookie in the cookie store
-        Cookie formauthCookie = getFormAuthCookieFromCookieStore();
-        assertNull("Did not expect formauth cookie in the cookie store", formauthCookie);
-
-        // verify that the script shows us as not logged in
-        HttpGet whoamiRequest = new HttpGet(whoamiUri());
-        try (CloseableHttpResponse whoamiResponse = httpClient.execute(whoamiRequest, httpContext)) {
-            assertEquals(HttpServletResponse.SC_OK, whoamiResponse.getStatusLine().getStatusCode());
-            String content = EntityUtils.toString(whoamiResponse.getEntity());
-            assertTrue(content.contains("whoAmI"));
-            assertTrue(content.contains("anonymous"));
-        }
-
-        // send the form login request
-        List<NameValuePair> parameters = new ArrayList<>();
-        parameters.add(new BasicNameValuePair("j_username", FORM_AUTH_VERIFY_USER));
-        parameters.add(new BasicNameValuePair("j_password", FORM_AUTH_VERIFY_PWD));
-        parameters.add(new BasicNameValuePair("_charset_", StandardCharsets.UTF_8.name()));
-        parameters.add(new BasicNameValuePair("resource", "/content.SLING10290IT.html"));
-        HttpPost request = new HttpPost(String.format("%s/j_security_check", baseServerUri));
-        request.setEntity(new UrlEncodedFormEntity(parameters));
-        Header locationHeader = null; 
-        try (CloseableHttpResponse response = httpClient.execute(request, httpContext)) {
-            assertEquals(HttpServletResponse.SC_MOVED_TEMPORARILY, response.getStatusLine().getStatusCode());
-            locationHeader = response.getFirstHeader("Location");
-
-            // verify that the expected set-cookie header arrived
-            Cookie parsedFormauthCookie = parseFormAuthCookieFromHeaders(response);
-            assertNotNull("Expected a formauth cookie in the response", parsedFormauthCookie);
-        }
-
-        // after login, there should be now be a cookie in the cookie store
-        Cookie formauthCookie2 = getFormAuthCookieFromCookieStore();
-        assertNotNull("Expected a formauth cookie in the cookie store", formauthCookie2);
-
-        // and then follow the redirect
-        assertNotNull("Expected a 'Location' header", locationHeader);
-        // verify that the script shows us logged in as the test user
-        HttpGet followedRequest = new HttpGet(locationHeader.getValue());
-        try (CloseableHttpResponse followedResponse = httpClient.execute(followedRequest, httpContext)) {
-            assertEquals(HttpServletResponse.SC_OK, followedResponse.getStatusLine().getStatusCode());
-            String content = EntityUtils.toString(followedResponse.getEntity());
-            assertTrue(content.contains("whoAmI"));
-            assertTrue(content.contains(FORM_AUTH_VERIFY_USER));
-
-            // there should be no new formauth cookie on the followed response
-            Cookie parsedFormauthCookie2 = parseFormAuthCookieFromHeaders(followedResponse);
-            assertNull("Did not expect a formauth cookie in the response", parsedFormauthCookie2);
-        }
-    }
-
-    /**
-     * Retrieve the formauth cookie from the cookie store
-     * 
-     * @return the formauth cookie or null if not found
-     */
-    private Cookie getFormAuthCookieFromCookieStore() {
-        Cookie formauthCookie = null;
-        List<Cookie> cookies = httpContext.getCookieStore().getCookies();
-        if (cookies != null) {
-            for (Cookie c : cookies) {
-                if (COOKIE_SLING_FORMAUTH.equals(c.getName())) {
-                    formauthCookie = c;
-                }
-            }
-        }
-        return formauthCookie;
-    }
-
-    /**
-     * Parse the formauth cookie out of the headers sent on the response
-     *
-     * @param response the response from the http request
-     * @return the found cookie or null if not found
-     */
-    private Cookie parseFormAuthCookieFromHeaders(HttpResponse response) throws MalformedCookieException {
-        Header [] cookieHeaders = response.getHeaders(HEADER_SET_COOKIE);
-        assertNotNull(cookieHeaders);
-
-        Cookie parsedFormauthCookie = null;
-        CookieSpec cookieSpec = new RFC6265StrictSpec();
-        CookieOrigin origin = new CookieOrigin(baseServerUri.getHost(), baseServerUri.getPort(),
-                baseServerUri.getPath(), "https".equals(baseServerUri.getScheme()));
-        for (Header cookieHeader : cookieHeaders) {
-            List<Cookie> parsedCookies = cookieSpec.parse(cookieHeader, origin);
-            for (Cookie c : parsedCookies) {
-                if (COOKIE_SLING_FORMAUTH.equals(c.getName())) {
-                    if (parsedFormauthCookie != null) {
-                        fail(String.format("Did not expect more than one %s cookie", c.getName()));
-                    }
-                    parsedFormauthCookie = c;
-                }
-            }
-        }
-        return parsedFormauthCookie;
-    }
-
 }
diff --git a/src/test/java/org/apache/sling/auth/form/it/SLING10421InvalidDomainIT.java b/src/test/java/org/apache/sling/auth/form/it/SLING10421InvalidDomainIT.java
new file mode 100644
index 0000000..d822b0a
--- /dev/null
+++ b/src/test/java/org/apache/sling/auth/form/it/SLING10421InvalidDomainIT.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.auth.form.it;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
+
+import java.io.IOException;
+import java.util.Date;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.http.client.CookieStore;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.cookie.Cookie;
+import org.apache.http.cookie.MalformedCookieException;
+import org.apache.http.impl.cookie.BasicClientCookie;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+
+/**
+ * SLING-10421 validate proper cookie handling when the formauth
+ * handler has been configured with an invalid cookie domain
+ */
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class SLING10421InvalidDomainIT extends AuthFormClientTestSupport {
+
+    @Override
+    protected Option newFormauthHandlerConfiguration() {
+        // change the default cookie domain config
+        return newConfiguration("org.apache.sling.auth.form.FormAuthenticationHandler")
+            .put("form.default.cookie.domain", "invalid")
+        .asOption();
+    }
+
+    /**
+     * SLING-10421 validate configured and client supplied cookie domain value
+     */
+    @Test
+    public void testLoginWithInvalidConfiguredCookieDomain() throws IOException, MalformedCookieException {
+        doFormsLogin();
+    }
+
+    /**
+     * SLING-10421 validate configured and client supplied cookie domain value
+     */
+    @Test
+    public void testLogoutWithInvalidConfiguredCookieDomain() throws IOException, MalformedCookieException {
+        doFormsLogin();
+
+        HttpGet logoutRequest = new HttpGet(String.format("%s/system/sling/logout", baseServerUri));
+        try (CloseableHttpResponse logoutResponse = httpClient.execute(logoutRequest, httpContext)) {
+            assertEquals(HttpServletResponse.SC_MOVED_TEMPORARILY, logoutResponse.getStatusLine().getStatusCode());
+            Cookie parsedFormauthCookie = parseFormAuthCookieFromHeaders(logoutResponse);
+            assertNotNull("Expected a formauth cookie in the response", parsedFormauthCookie);
+            assertEquals("Expected the formauth cookie value to be empty", "", parsedFormauthCookie.getValue());
+            assertTrue("Expected the formauth cookie to be expired", parsedFormauthCookie.isExpired(new Date()));
+            assertEquals("Expected the formauth cookie domain to be localhost", "localhost", parsedFormauthCookie.getDomain());
+
+            Cookie parsedFormauthDomainCookie = parseCookieFromHeaders(logoutResponse, COOKIE_SLING_FORMAUTH_DOMAIN);
+            assertNull("Did not expected a formauth domain cookie in the response", parsedFormauthDomainCookie);
+
+            Cookie formauthCookie2 = getFormAuthCookieFromCookieStore();
+            assertNull("Did not expected a formauth cookie in the cookie store", formauthCookie2);
+
+            Cookie formauthDomainCookie2 = getCookieFromCookieStore(COOKIE_SLING_FORMAUTH_DOMAIN);
+            assertNull("Did not expected a formauth domain cookie in the cookie store", formauthDomainCookie2);
+        }
+    }
+
+    /**
+     * SLING-10421 validate configured and client supplied cookie domain value
+     */
+    @Test
+    public void testLogoutWithInvalidDomainCookieValue() throws IOException, MalformedCookieException {
+        doFormsLogin();
+
+        // add an invalid domain cookie to the cookie store
+        CookieStore cookieStore = httpContext.getCookieStore();
+        BasicClientCookie invalidCookie = new BasicClientCookie(COOKIE_SLING_FORMAUTH_DOMAIN, "invalid");
+        invalidCookie.setPath("/");
+        invalidCookie.setDomain("localhost");
+        cookieStore.addCookie(invalidCookie);
+
+        HttpGet logoutRequest = new HttpGet(String.format("%s/system/sling/logout", baseServerUri));
+        try (CloseableHttpResponse logoutResponse = httpClient.execute(logoutRequest, httpContext)) {
+            assertEquals(HttpServletResponse.SC_MOVED_TEMPORARILY, logoutResponse.getStatusLine().getStatusCode());
+            Cookie parsedFormauthCookie = parseFormAuthCookieFromHeaders(logoutResponse);
+            assertNotNull("Expected a formauth cookie in the response", parsedFormauthCookie);
+            assertEquals("Expected the formauth cookie value to be empty", "", parsedFormauthCookie.getValue());
+            assertTrue("Expected the formauth cookie to be expired", parsedFormauthCookie.isExpired(new Date()));
+            assertEquals("Expected the formauth cookie domain to be localhost", "localhost", parsedFormauthCookie.getDomain());
+
+            Cookie parsedFormauthDomainCookie = parseCookieFromHeaders(logoutResponse, COOKIE_SLING_FORMAUTH_DOMAIN);
+            assertNull("Did not expected a formauth domain cookie in the response", parsedFormauthDomainCookie);
+
+            Cookie formauthCookie2 = getFormAuthCookieFromCookieStore();
+            assertNull("Did not expected a formauth cookie in the cookie store", formauthCookie2);
+
+            Cookie formauthDomainCookie2 = getCookieFromCookieStore(COOKIE_SLING_FORMAUTH_DOMAIN);
+            assertSame("Did not expected a new formauth domain cookie in the cookie store", invalidCookie, formauthDomainCookie2);
+        }
+    }
+
+    @Override
+    protected void doFormsLogin() throws MalformedCookieException, IOException {
+        super.doFormsLogin(cookie -> { 
+                assertEquals("Expected a formauth cookie with domain equal to host", "localhost", cookie.getDomain());
+            },
+            domainCookie -> {
+                assertNull("Did not expect a formauth domain cookie", domainCookie);
+            });
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/auth/form/it/SLING10421ValidDomainIT.java b/src/test/java/org/apache/sling/auth/form/it/SLING10421ValidDomainIT.java
new file mode 100644
index 0000000..116a3b5
--- /dev/null
+++ b/src/test/java/org/apache/sling/auth/form/it/SLING10421ValidDomainIT.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.auth.form.it;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
+
+import java.io.IOException;
+import java.util.Date;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.http.client.CookieStore;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.cookie.Cookie;
+import org.apache.http.cookie.MalformedCookieException;
+import org.apache.http.impl.cookie.BasicClientCookie;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+
+/**
+ * SLING-10421 validate proper cookie handling when the formauth
+ * handler has been configured with an valid cookie domain
+ */
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class SLING10421ValidDomainIT extends AuthFormClientTestSupport {
+
+    @Override
+    protected Option newFormauthHandlerConfiguration() {
+        // change the default cookie domain config
+        return newConfiguration("org.apache.sling.auth.form.FormAuthenticationHandler")
+            .put("form.default.cookie.domain", "localhost")
+        .asOption();
+    }
+
+    /**
+     * SLING-10421 validate configured and client supplied cookie domain value
+     */
+    @Test
+    public void testLoginWithValidConfiguredCookieDomain() throws IOException, MalformedCookieException {
+        doFormsLogin();
+    }
+
+    /**
+     * SLING-10421 validate configured and client supplied cookie domain value
+     */
+    @Test
+    public void testLogoutWithValidConfiguredCookieDomain() throws IOException, MalformedCookieException {
+        doFormsLogin();
+
+        HttpGet logoutRequest = new HttpGet(String.format("%s/system/sling/logout", baseServerUri));
+        try (CloseableHttpResponse logoutResponse = httpClient.execute(logoutRequest, httpContext)) {
+            assertEquals(HttpServletResponse.SC_MOVED_TEMPORARILY, logoutResponse.getStatusLine().getStatusCode());
+            Cookie parsedFormauthCookie = parseFormAuthCookieFromHeaders(logoutResponse);
+            assertNotNull("Expected a formauth cookie in the response", parsedFormauthCookie);
+            assertEquals("Expected the formauth cookie value to be empty", "", parsedFormauthCookie.getValue());
+            assertTrue("Expected the formauth cookie to be expired", parsedFormauthCookie.isExpired(new Date()));
+            assertEquals("Expected the formauth cookie domain to be localhost", "localhost", parsedFormauthCookie.getDomain());
+
+            Cookie parsedFormauthDomainCookie = parseCookieFromHeaders(logoutResponse, COOKIE_SLING_FORMAUTH_DOMAIN);
+            assertNotNull("Expected a formauth domain cookie in the response", parsedFormauthDomainCookie);
+            assertEquals("Expected the formauth domain cookie value to be empty", "", parsedFormauthDomainCookie.getValue());
+            assertTrue("Expected the formauth domain cookie to be expired", parsedFormauthDomainCookie.isExpired(new Date()));
+            assertEquals("Expected the formauth cookie domain to be localhost", "localhost", parsedFormauthDomainCookie.getDomain());
+
+            Cookie formauthCookie2 = getFormAuthCookieFromCookieStore();
+            assertNull("Did not expected a formauth cookie in the cookie store", formauthCookie2);
+
+            Cookie formauthDomainCookie2 = getCookieFromCookieStore(COOKIE_SLING_FORMAUTH_DOMAIN);
+            assertNull("Did not expected a formauth domain cookie in the cookie store", formauthDomainCookie2);
+        }
+    }
+
+    /**
+     * SLING-10421 validate configured and client supplied cookie domain value
+     */
+    @Test
+    public void testLogoutWithInvalidDomainCookieValue() throws IOException, MalformedCookieException {
+        doFormsLogin();
+
+        // add an invalid domain cookie to the cookie store
+        CookieStore cookieStore = httpContext.getCookieStore();
+        BasicClientCookie invalidCookie = new BasicClientCookie(COOKIE_SLING_FORMAUTH_DOMAIN, "invalid");
+        invalidCookie.setPath("/");
+        invalidCookie.setDomain("localhost");
+        cookieStore.addCookie(invalidCookie);
+
+        HttpGet logoutRequest = new HttpGet(String.format("%s/system/sling/logout", baseServerUri));
+        try (CloseableHttpResponse logoutResponse = httpClient.execute(logoutRequest, httpContext)) {
+            assertEquals(HttpServletResponse.SC_MOVED_TEMPORARILY, logoutResponse.getStatusLine().getStatusCode());
+            Cookie parsedFormauthCookie = parseFormAuthCookieFromHeaders(logoutResponse);
+            assertNotNull("Expected a formauth cookie in the response", parsedFormauthCookie);
+            assertEquals("Expected the formauth cookie value to be empty", "", parsedFormauthCookie.getValue());
+            assertTrue("Expected the formauth cookie to be expired", parsedFormauthCookie.isExpired(new Date()));
+            assertEquals("Expected the formauth cookie domain to be localhost", "localhost", parsedFormauthCookie.getDomain());
+
+            Cookie parsedFormauthDomainCookie = parseCookieFromHeaders(logoutResponse, COOKIE_SLING_FORMAUTH_DOMAIN);
+            assertNotNull("Expected a formauth domain cookie in the response", parsedFormauthDomainCookie);
+            assertEquals("Expected the formauth domain cookie value to be empty", "", parsedFormauthDomainCookie.getValue());
+            assertTrue("Expected the formauth domain cookie to be expired", parsedFormauthDomainCookie.isExpired(new Date()));
+            assertEquals("Expected the formauth cookie domain to be localhost", "localhost", parsedFormauthDomainCookie.getDomain());
+
+            Cookie formauthCookie2 = getFormAuthCookieFromCookieStore();
+            assertNull("Did not expected a formauth cookie in the cookie store", formauthCookie2);
+
+            Cookie formauthDomainCookie2 = getCookieFromCookieStore(COOKIE_SLING_FORMAUTH_DOMAIN);
+            assertNull("Did not expected a formauth domain cookie in the cookie store", formauthDomainCookie2);
+        }
+    }
+
+    @Override
+    protected void doFormsLogin() throws MalformedCookieException, IOException {
+        super.doFormsLogin(cookie -> { 
+                assertEquals("Expected a formauth cookie with domain equal to localhost", "localhost", cookie.getDomain());
+            },
+            domainCookie -> {
+                assertNotNull("Expected a formauth domain cookie", domainCookie);
+                assertEquals("Expected the domain cookie value to be localhost", "localhost", domainCookie.getValue());
+            });
+    }
+
+}