diff --git a/flow-tests/pom.xml b/flow-tests/pom.xml
index 56394233615..f267147a190 100644
--- a/flow-tests/pom.xml
+++ b/flow-tests/pom.xml
@@ -335,6 +335,8 @@
test-ccdm/pom-production.xml
test-ccdm-flow-navigation
test-ccdm-flow-navigation/pom-production.xml
+ test-fusion-csrf
+ test-fusion-csrf-context
test-root-context
diff --git a/flow-tests/test-fusion-csrf-context/.gitignore b/flow-tests/test-fusion-csrf-context/.gitignore
new file mode 100644
index 00000000000..b123cfef890
--- /dev/null
+++ b/flow-tests/test-fusion-csrf-context/.gitignore
@@ -0,0 +1,3 @@
+
+# /
+frontend
diff --git a/flow-tests/test-fusion-csrf-context/pom.xml b/flow-tests/test-fusion-csrf-context/pom.xml
new file mode 100644
index 00000000000..d1d66b5e0cb
--- /dev/null
+++ b/flow-tests/test-fusion-csrf-context/pom.xml
@@ -0,0 +1,151 @@
+
+
+
+ com.vaadin
+ flow-tests
+ 9.0-SNAPSHOT
+
+ 4.0.0
+
+ test-fusion-csrf-context
+ Fusion CSRF tests with custom context path
+
+ For https://github.com/vaadin/fusion/issues/105.
+ It verifies that the csrf cookie is added to correct path
+ when first open a sub view.
+ Note, the test needs to be in a separate module because
+ it needs to open a sub view first, put the test into another
+ module cannot gurantee this.
+
+ 9.0-SNAPSHOT
+ war
+
+
+ true
+
+
+
+
+
+ com.vaadin
+ fusion-endpoint
+ ${project.version}
+
+
+ com.vaadin
+ vaadin-dev-server
+ ${project.version}
+
+
+ com.vaadin
+ test-fusion-csrf
+ ${project.version}
+ frontend
+
+
+ com.vaadin
+ test-fusion-csrf
+ ${project.version}
+ test-jar
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ unpack
+ generate-sources
+
+ unpack
+
+
+
+
+ com.vaadin
+ test-fusion-csrf
+ ${project.version}
+ frontend
+ ${project.basedir}/frontend
+
+
+
+
+
+
+
+
+ org.eclipse.jetty
+ jetty-maven-plugin
+
+
+
+
+ vaadin.reuseDevServer
+ false
+
+
+
+ /foo
+
+
+
+
+ com.vaadin
+ flow-maven-plugin
+
+ false
+
+
+
+
+
+
+ local-run
+
+
+ !test.use.hub
+
+
+
+
+
+ com.lazerycode.selenium
+ driver-binary-downloader-maven-plugin
+
+
+ ${driver.binary.downloader.maven.plugin.version}
+
+
+ true
+
+
+ ${project.rootdir}/driver
+
+
+ ${project.rootdir}/driver_zips
+
+
+ ${project.rootdir}/drivers.xml
+
+
+
+
+ pre-integration-test
+
+ selenium
+
+
+
+
+
+
+
+
+
diff --git a/flow-tests/test-fusion-csrf-context/src/main/java/com/vaadin/fusion/csrftest/MyServlet.java b/flow-tests/test-fusion-csrf-context/src/main/java/com/vaadin/fusion/csrftest/MyServlet.java
new file mode 100644
index 00000000000..4b44872e569
--- /dev/null
+++ b/flow-tests/test-fusion-csrf-context/src/main/java/com/vaadin/fusion/csrftest/MyServlet.java
@@ -0,0 +1,10 @@
+package com.vaadin.fusion.csrftest;
+
+import javax.servlet.annotation.WebServlet;
+
+import com.vaadin.flow.server.VaadinServlet;
+
+@WebServlet("/*")
+public class MyServlet extends VaadinServlet {
+
+}
diff --git a/flow-tests/test-fusion-csrf-context/src/test/java/com/vaadin/fusion/csrftest/CsrfCookieWithTrailingSlashAndCustomContextIT.java b/flow-tests/test-fusion-csrf-context/src/test/java/com/vaadin/fusion/csrftest/CsrfCookieWithTrailingSlashAndCustomContextIT.java
new file mode 100644
index 00000000000..80f3a8f1f5e
--- /dev/null
+++ b/flow-tests/test-fusion-csrf-context/src/test/java/com/vaadin/fusion/csrftest/CsrfCookieWithTrailingSlashAndCustomContextIT.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2000-2021 Vaadin Ltd.
+ *
+ * Licensed 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 com.vaadin.fusion.csrftest;
+
+public class CsrfCookieWithTrailingSlashAndCustomContextIT
+ extends CsrfCookieWithTrailingSlashIT {
+ @Override
+ protected String getContextPath() {
+ return "/foo";
+ }
+}
diff --git a/flow-tests/test-fusion-csrf/frontend/index.html b/flow-tests/test-fusion-csrf/frontend/index.html
new file mode 100644
index 00000000000..caa0ebe667e
--- /dev/null
+++ b/flow-tests/test-fusion-csrf/frontend/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+ fusion-csrf
+
+
+
+
+
+
+
+
diff --git a/flow-tests/test-fusion-csrf/frontend/index.ts b/flow-tests/test-fusion-csrf/frontend/index.ts
new file mode 100644
index 00000000000..276df86dfa0
--- /dev/null
+++ b/flow-tests/test-fusion-csrf/frontend/index.ts
@@ -0,0 +1,17 @@
+import { Router, Route } from '@vaadin/router';
+import './views/helloworld/hello-world-view';
+
+export const router = new Router(document.querySelector('#outlet'));
+
+export const routes: Route[] = [
+ // place routes below (more info https://vaadin.com/docs/latest/fusion/routing/overview)
+ {
+ path: '',
+ component: 'hello-world-view',
+ },
+ {
+ path: 'hello',
+ component: 'hello-world-view',
+ }
+];
+router.setRoutes(routes);
\ No newline at end of file
diff --git a/flow-tests/test-fusion-csrf/frontend/views/helloworld/hello-world-view.ts b/flow-tests/test-fusion-csrf/frontend/views/helloworld/hello-world-view.ts
new file mode 100644
index 00000000000..0aa0a2bd324
--- /dev/null
+++ b/flow-tests/test-fusion-csrf/frontend/views/helloworld/hello-world-view.ts
@@ -0,0 +1,11 @@
+import { html, LitElement } from 'lit';
+import { customElement } from 'lit/decorators.js';
+
+@customElement('hello-world-view')
+export class HelloWorldView extends LitElement {
+ render() {
+ return html`
+ HelloWorldView
+ `;
+ }
+}
diff --git a/flow-tests/test-fusion-csrf/pom.xml b/flow-tests/test-fusion-csrf/pom.xml
new file mode 100644
index 00000000000..018e32b142a
--- /dev/null
+++ b/flow-tests/test-fusion-csrf/pom.xml
@@ -0,0 +1,133 @@
+
+
+
+ com.vaadin
+ flow-tests
+ 9.0-SNAPSHOT
+
+ 4.0.0
+
+ test-fusion-csrf
+ Fusion CSRF tests (dev mode)
+
+ For https://github.com/vaadin/fusion/issues/105.
+ It verifies that the csrf cookie is added to correct path
+ when first open a sub view.
+ Note, the test needs to be in a separate module because
+ it needs to open a sub view first, put the test into another
+ module cannot gurantee this.
+
+ 9.0-SNAPSHOT
+ war
+
+
+ true
+
+
+
+
+
+ com.vaadin
+ fusion-endpoint
+ ${project.version}
+
+
+ com.vaadin
+ vaadin-dev-server
+ ${project.version}
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.0.2
+
+
+
+ test-jar
+
+
+
+ package-frontend-folder
+
+ jar
+
+
+ frontend
+ ${project.basedir}/frontend
+
+
+
+
+
+
+ org.eclipse.jetty
+ jetty-maven-plugin
+
+
+
+
+ vaadin.reuseDevServer
+ false
+
+
+
+
+
+ com.vaadin
+ flow-maven-plugin
+
+ false
+
+
+
+
+
+
+ local-run
+
+
+ !test.use.hub
+
+
+
+
+
+ com.lazerycode.selenium
+ driver-binary-downloader-maven-plugin
+
+
+ ${driver.binary.downloader.maven.plugin.version}
+
+
+ true
+
+
+ ${project.rootdir}/driver
+
+
+ ${project.rootdir}/driver_zips
+
+
+ ${project.rootdir}/drivers.xml
+
+
+
+
+ pre-integration-test
+
+ selenium
+
+
+
+
+
+
+
+
+
diff --git a/flow-tests/test-fusion-csrf/src/main/java/com/vaadin/fusion/csrftest/MyServlet.java b/flow-tests/test-fusion-csrf/src/main/java/com/vaadin/fusion/csrftest/MyServlet.java
new file mode 100644
index 00000000000..4b44872e569
--- /dev/null
+++ b/flow-tests/test-fusion-csrf/src/main/java/com/vaadin/fusion/csrftest/MyServlet.java
@@ -0,0 +1,10 @@
+package com.vaadin.fusion.csrftest;
+
+import javax.servlet.annotation.WebServlet;
+
+import com.vaadin.flow.server.VaadinServlet;
+
+@WebServlet("/*")
+public class MyServlet extends VaadinServlet {
+
+}
diff --git a/flow-tests/test-fusion-csrf/src/test/java/com/vaadin/fusion/csrftest/CsrfCookieWithTrailingSlashIT.java b/flow-tests/test-fusion-csrf/src/test/java/com/vaadin/fusion/csrftest/CsrfCookieWithTrailingSlashIT.java
new file mode 100644
index 00000000000..f23574c126b
--- /dev/null
+++ b/flow-tests/test-fusion-csrf/src/test/java/com/vaadin/fusion/csrftest/CsrfCookieWithTrailingSlashIT.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2000-2021 Vaadin Ltd.
+ *
+ * Licensed 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 com.vaadin.fusion.csrftest;
+
+import java.io.IOException;
+import java.net.CookieHandler;
+import java.net.CookieManager;
+import java.net.CookieStore;
+import java.net.HttpCookie;
+import java.net.URL;
+import java.net.URLConnection;
+
+import com.vaadin.flow.testutil.ChromeBrowserTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CsrfCookieWithTrailingSlashIT extends ChromeBrowserTest {
+ @Test
+ // https://github.com/vaadin/fusion/issues/105
+ public void should_registerCsrfCookieToContextRoot_whenRequestFromSubViewAndUrlHasTrailingSlash()
+ throws IOException {
+ CookieManager cookieManager = new CookieManager();
+ CookieHandler.setDefault(cookieManager);
+
+ open();
+
+ URL url = new URL(getTestURL());
+ URLConnection urlConnection = url.openConnection();
+ urlConnection.getContent();
+ // Get CookieStore
+ CookieStore cookieStore = cookieManager.getCookieStore();
+
+ HttpCookie csrfCookie = cookieStore.getCookies().stream()
+ .filter(cookie -> "csrfToken".equals(cookie.getName()))
+ .findFirst().get();
+ Assert.assertEquals(getContextPath(), csrfCookie.getPath());
+ }
+
+ @Override
+ protected String getTestPath() {
+ return getContextPath() + ("/".equals(getContextPath()) ? "" : "/")
+ + "hello/";
+ }
+
+ protected String getContextPath() {
+ return "/";
+ }
+}
diff --git a/fusion-endpoint/src/main/java/com/vaadin/fusion/auth/CsrfIndexHtmlRequestListener.java b/fusion-endpoint/src/main/java/com/vaadin/fusion/auth/CsrfIndexHtmlRequestListener.java
index b5fde0df017..3d5cf133b38 100644
--- a/fusion-endpoint/src/main/java/com/vaadin/fusion/auth/CsrfIndexHtmlRequestListener.java
+++ b/fusion-endpoint/src/main/java/com/vaadin/fusion/auth/CsrfIndexHtmlRequestListener.java
@@ -66,7 +66,11 @@ private void ensureCsrfTokenCookieIsSet(VaadinRequest request,
Cookie csrfCookie = new Cookie(ApplicationConstants.CSRF_TOKEN,
csrfToken);
csrfCookie.setSecure(request.isSecure());
- csrfCookie.setPath(request.getContextPath());
+ String path = request.getContextPath();
+ if (path == null || path.isEmpty()) {
+ path = "/";
+ }
+ csrfCookie.setPath(path);
csrfCookie.setHttpOnly(false);
response.addCookie(csrfCookie);
}