NIFI-9060 Refactored HTTP Cookie Path Handling

- Implemented ApplicationCookieService for adding and retrieving HTTP Cookies
- Added getCookieResourceUri() leveraging allowed proxy headers to support optional Cookie Paths
- Refactored Access Resources to use ApplicationCookieService for processing
- Changed __Host- prefix to __Secure- prefix for Bearer Token cookie to support Cookie Path processing
- Removed unnecessary jetty-http dependency from nifi-web-api
- Corrected NiFi path references in JavaScript to support prefixed paths

Signed-off-by: Nathan Gough <thenatog@gmail.com>

This closes #5329.
diff --git a/nifi-commons/nifi-web-utils/src/main/java/org/apache/nifi/web/util/WebUtils.java b/nifi-commons/nifi-web-utils/src/main/java/org/apache/nifi/web/util/WebUtils.java
index 5e508e3..32dbb53 100644
--- a/nifi-commons/nifi-web-utils/src/main/java/org/apache/nifi/web/util/WebUtils.java
+++ b/nifi-commons/nifi-web-utils/src/main/java/org/apache/nifi/web/util/WebUtils.java
@@ -32,8 +32,6 @@
 import java.net.URI;
 import java.util.Arrays;
 import java.util.List;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.stream.Stream;
 
 /**
@@ -43,8 +41,6 @@
 
     private static final Logger logger = LoggerFactory.getLogger(WebUtils.class);
 
-    final static ReadWriteLock lock = new ReentrantReadWriteLock();
-
     public static final String PROXY_SCHEME_HTTP_HEADER = "X-ProxyScheme";
     public static final String PROXY_HOST_HTTP_HEADER = "X-ProxyHost";
     public static final String PROXY_PORT_HTTP_HEADER = "X-ProxyPort";
@@ -57,6 +53,8 @@
     public static final String FORWARDED_CONTEXT_HTTP_HEADER = "X-Forwarded-Context";
     public static final String FORWARDED_PREFIX_HTTP_HEADER = "X-Forwarded-Prefix";
 
+    private static final String EMPTY = "";
+
     private WebUtils() {
     }
 
@@ -207,7 +205,7 @@
             return contextPath;
         } catch (UriBuilderException e) {
             logger.error("Error determining context path on " + jspDisplayName + ": " + e.getMessage());
-            return "";
+            return EMPTY;
         }
     }
 
@@ -218,27 +216,18 @@
      * @param request the HTTP request
      * @return the provided context path or an empty string
      */
-    public static String determineContextPath(HttpServletRequest request) {
-        String contextPath = request.getContextPath();
-        String proxyContextPath = request.getHeader(PROXY_CONTEXT_PATH_HTTP_HEADER);
-        String forwardedContext = request.getHeader(FORWARDED_CONTEXT_HTTP_HEADER);
-        String prefix = request.getHeader(FORWARDED_PREFIX_HTTP_HEADER);
+    public static String determineContextPath(final HttpServletRequest request) {
+        final String proxyContextPath = request.getHeader(PROXY_CONTEXT_PATH_HTTP_HEADER);
+        final String forwardedContext = request.getHeader(FORWARDED_CONTEXT_HTTP_HEADER);
+        final String prefix = request.getHeader(FORWARDED_PREFIX_HTTP_HEADER);
 
-        logger.debug("Context path: " + contextPath);
-        String determinedContextPath = "";
-
-        // If a context path header is set, log each
+        String determinedContextPath = EMPTY;
         if (anyNotBlank(proxyContextPath, forwardedContext, prefix)) {
-            logger.debug(String.format("On the request, the following context paths were parsed" +
-                            " from headers:\n\t X-ProxyContextPath: %s\n\tX-Forwarded-Context: %s\n\tX-Forwarded-Prefix: %s",
-                    proxyContextPath, forwardedContext, prefix));
-
             // Implementing preferred order here: PCP, FC, FP
             determinedContextPath = Stream.of(proxyContextPath, forwardedContext, prefix)
-                    .filter(StringUtils::isNotBlank).findFirst().orElse("");
+                    .filter(StringUtils::isNotBlank).findFirst().orElse(EMPTY);
         }
 
-        logger.debug("Determined context path: " + determinedContextPath);
         return determinedContextPath;
     }
 
@@ -366,7 +355,7 @@
             portFromHostHeader = null;
         }
         if (StringUtils.isNotBlank(portFromHostHeader) && StringUtils.isNotBlank(portHeaderValue)) {
-            logger.warn(String.format("The proxied host header contained a port, but was overridden by the proxied port header"));
+            logger.warn("Forwarded Host Port [{}] replaced with Forwarded Port [{}]", portFromHostHeader, portHeaderValue);
         }
         port = StringUtils.isNotBlank(portHeaderValue) ? portHeaderValue : (StringUtils.isNotBlank(portFromHostHeader) ? portFromHostHeader : null);
         return port;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml
index c06fbdc..f62cedf 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml
@@ -432,11 +432,5 @@
             <groupId>org.slf4j</groupId>
             <artifactId>jcl-over-slf4j</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.eclipse.jetty</groupId>
-            <artifactId>jetty-http</artifactId>
-            <version>${jetty.version}</version>
-            <scope>compile</scope>
-        </dependency>
     </dependencies>
 </project>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
index 645d679..f05dbee 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
@@ -34,6 +34,7 @@
 import org.apache.nifi.authorization.user.NiFiUserUtils;
 import org.apache.nifi.authorization.util.IdentityMappingUtil;
 import org.apache.nifi.util.FormatUtils;
+import org.apache.nifi.web.api.cookie.ApplicationCookieName;
 import org.apache.nifi.web.api.dto.AccessConfigurationDTO;
 import org.apache.nifi.web.api.dto.AccessStatusDTO;
 import org.apache.nifi.web.api.entity.AccessConfigurationEntity;
@@ -42,7 +43,6 @@
 import org.apache.nifi.web.security.LogoutException;
 import org.apache.nifi.web.security.ProxiedEntitiesUtils;
 import org.apache.nifi.web.security.UntrustedProxyException;
-import org.apache.nifi.web.security.http.SecurityCookieName;
 import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider;
 import org.apache.nifi.web.security.jwt.revocation.JwtLogoutListener;
 import org.apache.nifi.web.security.kerberos.KerberosService;
@@ -62,9 +62,7 @@
 import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
 import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
 import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
-import org.springframework.web.util.WebUtils;
 
-import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.Consumes;
@@ -81,6 +79,7 @@
 import javax.ws.rs.core.UriBuilder;
 import java.net.URI;
 import java.security.cert.X509Certificate;
+import java.util.Optional;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
@@ -96,7 +95,6 @@
 
     private static final Logger logger = LoggerFactory.getLogger(AccessResource.class);
     protected static final String AUTHENTICATION_NOT_ENABLED_MSG = "User authentication/authorization is only supported when running over HTTPS.";
-    static final String LOGOUT_REQUEST_IDENTIFIER = "nifi-logout-request-identifier";
 
     private X509CertificateExtractor certificateExtractor;
     private X509AuthenticationProvider x509AuthenticationProvider;
@@ -259,7 +257,7 @@
                     accessStatus.setStatus(AccessStatusDTO.Status.UNKNOWN.name());
                     accessStatus.setMessage("Access Unknown: Authorization Header not found.");
                     // Remove Session Cookie when Authorization Header not found
-                    removeCookie(httpServletResponse, SecurityCookieName.AUTHORIZATION_BEARER.getName());
+                    applicationCookieService.removeCookie(getCookieResourceUri(), httpServletResponse, ApplicationCookieName.AUTHORIZATION_BEARER);
                 } else {
                     try {
                         // authenticate the token
@@ -275,10 +273,7 @@
                         accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name());
                         accessStatus.setMessage("You are already logged in.");
                     } catch (final AuthenticationException iae) {
-                        if (WebUtils.getCookie(httpServletRequest, SecurityCookieName.AUTHORIZATION_BEARER.getName()) != null) {
-                            removeCookie(httpServletResponse, SecurityCookieName.AUTHORIZATION_BEARER.getName());
-                        }
-
+                        applicationCookieService.removeCookie(getCookieResourceUri(), httpServletResponse, ApplicationCookieName.AUTHORIZATION_BEARER);
                         throw iae;
                     }
                 }
@@ -343,7 +338,7 @@
                     @ApiResponse(code = 500, message = "Unable to create access token because an unexpected error occurred.")
             }
     )
-    public Response createAccessTokenFromTicket(@Context HttpServletRequest httpServletRequest) {
+    public Response createAccessTokenFromTicket(@Context final HttpServletRequest httpServletRequest, @Context final HttpServletResponse httpServletResponse) {
 
         // only support access tokens when communicating over HTTPS
         if (!httpServletRequest.isSecure()) {
@@ -379,7 +374,8 @@
                 final LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken(mappedIdentity, expiration, "KerberosService");
                 final String token = bearerTokenProvider.getBearerToken(loginAuthenticationToken);
                 final URI uri = URI.create(generateResourceUri("access", "kerberos"));
-                return generateTokenResponse(generateCreatedResponse(uri, token), token);
+                setBearerToken(httpServletResponse, token);
+                return generateCreatedResponse(uri, token).build();
             } catch (final AuthenticationException e) {
                 throw new AccessDeniedException(e.getMessage(), e);
             }
@@ -414,9 +410,10 @@
             }
     )
     public Response createAccessToken(
-            @Context HttpServletRequest httpServletRequest,
-            @FormParam("username") String username,
-            @FormParam("password") String password) {
+            @Context final HttpServletRequest httpServletRequest,
+            @Context final HttpServletResponse httpServletResponse,
+            @FormParam("username") final String username,
+            @FormParam("password") final String password) {
 
         // only support access tokens when communicating over HTTPS
         if (!httpServletRequest.isSecure()) {
@@ -450,9 +447,10 @@
             throw new AdministrationException(iae.getMessage(), iae);
         }
 
-        final String token = bearerTokenProvider.getBearerToken(loginAuthenticationToken);
+        final String bearerToken = bearerTokenProvider.getBearerToken(loginAuthenticationToken);
         final URI uri = URI.create(generateResourceUri("access", "token"));
-        return generateTokenResponse(generateCreatedResponse(uri, token), token);
+        setBearerToken(httpServletResponse, bearerToken);
+        return generateCreatedResponse(uri, bearerToken).build();
     }
 
     @DELETE
@@ -484,7 +482,7 @@
         try {
             logger.info("Logout Started [{}]", mappedUserIdentity);
             logger.debug("Removing Authorization Cookie [{}]", mappedUserIdentity);
-            removeCookie(httpServletResponse, SecurityCookieName.AUTHORIZATION_BEARER.getName());
+            applicationCookieService.removeCookie(getCookieResourceUri(), httpServletResponse, ApplicationCookieName.AUTHORIZATION_BEARER);
 
             final String bearerToken = bearerTokenResolver.resolve(httpServletRequest);
             jwtLogoutListener.logout(bearerToken);
@@ -493,14 +491,7 @@
             final LogoutRequest logoutRequest = new LogoutRequest(UUID.randomUUID().toString(), mappedUserIdentity);
             logoutRequestManager.start(logoutRequest);
 
-            // generate a cookie to store the logout request identifier
-            final Cookie cookie = new Cookie(LOGOUT_REQUEST_IDENTIFIER, logoutRequest.getRequestIdentifier());
-            cookie.setPath("/");
-            cookie.setHttpOnly(true);
-            cookie.setMaxAge(60);
-            cookie.setSecure(true);
-            httpServletResponse.addCookie(cookie);
-
+            applicationCookieService.addCookie(getCookieResourceUri(), httpServletResponse, ApplicationCookieName.LOGOUT_REQUEST_IDENTIFIER, logoutRequest.getRequestIdentifier());
             return generateOkResponse().build();
         } catch (final LogoutException e) {
             logger.error("Logout Failed Identity [{}]", mappedUserIdentity, e);
@@ -538,22 +529,16 @@
     LogoutRequest completeLogoutRequest(final HttpServletResponse httpServletResponse) {
         LogoutRequest logoutRequest = null;
 
-        // check if a logout request identifier is present and if so complete the request
-        final Cookie cookie = WebUtils.getCookie(httpServletRequest, LOGOUT_REQUEST_IDENTIFIER);
-        final String logoutRequestIdentifier = cookie == null ? null : cookie.getValue();
-        if (logoutRequestIdentifier != null) {
+        final Optional<String> cookieValue = getLogoutRequestIdentifier();
+        if (cookieValue.isPresent()) {
+            final String logoutRequestIdentifier = cookieValue.get();
             logoutRequest = logoutRequestManager.complete(logoutRequestIdentifier);
-        }
-
-        if (logoutRequest == null) {
-            logger.warn("Logout Request [{}] not found", logoutRequestIdentifier);
-        } else {
             logger.info("Logout Request [{}] Completed [{}]", logoutRequestIdentifier, logoutRequest.getMappedUserIdentity());
+        } else {
+            logger.warn("Logout Request Cookie [{}] not found", ApplicationCookieName.LOGOUT_REQUEST_IDENTIFIER.getCookieName());
         }
 
-        // remove the cookie if it existed
         removeLogoutRequestCookie(httpServletResponse);
-
         return logoutRequest;
     }
 
@@ -578,8 +563,22 @@
         return getNiFiUri() + "logout-complete";
     }
 
-    void removeLogoutRequestCookie(final HttpServletResponse httpServletResponse) {
-        removeCookie(httpServletResponse, LOGOUT_REQUEST_IDENTIFIER);
+    /**
+     * Send Set-Cookie header to remove Logout Request Identifier cookie from client
+     *
+     * @param httpServletResponse HTTP Servlet Response
+     */
+    protected void removeLogoutRequestCookie(final HttpServletResponse httpServletResponse) {
+        applicationCookieService.removeCookie(getCookieResourceUri(), httpServletResponse, ApplicationCookieName.LOGOUT_REQUEST_IDENTIFIER);
+    }
+
+    /**
+     * Get Logout Request Identifier from current HTTP Request Cookie header
+     *
+     * @return Optional Logout Request Identifier
+     */
+    protected Optional<String> getLogoutRequestIdentifier() {
+        return applicationCookieService.getCookieValue(httpServletRequest, ApplicationCookieName.LOGOUT_REQUEST_IDENTIFIER);
     }
 
     // setters
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java
index f2f6cc3..ad95819 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java
@@ -50,21 +50,20 @@
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.NiFiServiceFacade;
 import org.apache.nifi.web.Revision;
+import org.apache.nifi.web.api.cookie.ApplicationCookieName;
+import org.apache.nifi.web.api.cookie.ApplicationCookieService;
+import org.apache.nifi.web.api.cookie.StandardApplicationCookieService;
 import org.apache.nifi.web.api.dto.RevisionDTO;
 import org.apache.nifi.web.api.entity.ComponentEntity;
 import org.apache.nifi.web.api.entity.Entity;
 import org.apache.nifi.web.api.entity.TransactionResultEntity;
 import org.apache.nifi.web.security.ProxiedEntitiesUtils;
-import org.apache.nifi.web.security.http.SecurityCookieName;
 import org.apache.nifi.web.security.util.CacheKey;
 import org.apache.nifi.web.util.WebUtils;
-import org.eclipse.jetty.http.HttpCookie;
-import org.eclipse.jetty.http.HttpHeader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.servlet.ServletContext;
-import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.core.CacheControl;
@@ -114,12 +113,13 @@
     public static final String DISCONNECTED_NODE_ACKNOWLEDGED = "disconnectedNodeAcknowledged";
     static final String LOGIN_ERROR_TITLE = "Unable to continue login sequence";
     static final String LOGOUT_ERROR_TITLE = "Unable to continue logout sequence";
-    private static final int VALID_FOR_SESSION_ONLY = -1;
 
     protected static final String NON_GUARANTEED_ENDPOINT = "Note: This endpoint is subject to change as NiFi and it's REST API evolve.";
 
     private static final Logger logger = LoggerFactory.getLogger(ApplicationResource.class);
 
+    private static final String ROOT_PATH = "/";
+
     public static final String NODEWISE = "false";
 
     @Context
@@ -128,6 +128,7 @@
     @Context
     protected UriInfo uriInfo;
 
+    protected ApplicationCookieService applicationCookieService = new StandardApplicationCookieService();
     protected NiFiProperties properties;
     private RequestReplicator requestReplicator;
     private ClusterCoordinator clusterCoordinator;
@@ -164,14 +165,23 @@
         return uri.toString();
     }
 
+    /**
+     * Get Resource URI used for Cookie Domain and Path properties
+     *
+     * @return Cookie Resource URI
+     */
+    protected URI getCookieResourceUri() {
+        final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
+        return buildResourceUri(uriBuilder.replacePath(ROOT_PATH).build());
+    }
+
     private URI buildResourceUri(final String... path) {
         final UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
-        uriBuilder.segment(path);
-        URI uri = uriBuilder.build();
+        return buildResourceUri(uriBuilder.segment(path).build());
+    }
+
+    private URI buildResourceUri(final URI uri) {
         try {
-
-            // check for proxy settings
-
             final String scheme = getFirstHeaderValue(PROXY_SCHEME_HTTP_HEADER, FORWARDED_PROTO_HTTP_HEADER);
             final String hostHeaderValue = getFirstHeaderValue(PROXY_HOST_HTTP_HEADER, FORWARDED_HOST_HTTP_HEADER);
             final String portHeaderValue = getFirstHeaderValue(PROXY_PORT_HTTP_HEADER, FORWARDED_PORT_HTTP_HEADER);
@@ -180,25 +190,20 @@
             final String port = WebUtils.determineProxiedPort(hostHeaderValue, portHeaderValue);
 
             // Catch header poisoning
-            String allowedContextPaths = properties.getAllowedContextPaths();
-            String resourcePath = WebUtils.getResourcePath(uri, httpServletRequest, allowedContextPaths);
+            final String allowedContextPaths = properties.getAllowedContextPaths();
+            final String resourcePath = WebUtils.getResourcePath(uri, httpServletRequest, allowedContextPaths);
 
             // determine the port uri
             int uriPort = uri.getPort();
-            if (port != null) {
-                if (StringUtils.isWhitespace(port)) {
-                    uriPort = -1;
-                } else {
-                    try {
-                        uriPort = Integer.parseInt(port);
-                    } catch (final NumberFormatException nfe) {
-                        logger.warn(String.format("Unable to parse proxy port HTTP header '%s'. Using port from request URI '%s'.", port, uriPort));
-                    }
+            if (StringUtils.isNumeric(port)) {
+                try {
+                    uriPort = Integer.parseInt(port);
+                } catch (final NumberFormatException nfe) {
+                    logger.warn("Parsing Proxy Port [{}] Failed: Using URI Port [{}]", port, uriPort);
                 }
             }
 
-            // construct the URI
-            uri = new URI(
+            return new URI(
                     (StringUtils.isBlank(scheme)) ? uri.getScheme() : scheme,
                     uri.getUserInfo(),
                     (StringUtils.isBlank(host)) ? uri.getHost() : host,
@@ -206,11 +211,9 @@
                     resourcePath,
                     uri.getQuery(),
                     uri.getFragment());
-
         } catch (final URISyntaxException use) {
             throw new UriBuilderException(use);
         }
-        return uri;
     }
 
     /**
@@ -314,7 +317,7 @@
     }
 
     protected MultivaluedMap<String, String> getRequestParameters() {
-        final MultivaluedMap<String, String> entity = new MultivaluedHashMap();
+        final MultivaluedMap<String, String> entity = new MultivaluedHashMap<>();
 
         for (final Map.Entry<String, String[]> entry : httpServletRequest.getParameterMap().entrySet()) {
             if (entry.getValue() == null) {
@@ -330,7 +333,7 @@
     }
 
     protected Map<String, String> getHeaders() {
-        return getHeaders(new HashMap<String, String>());
+        return getHeaders(new HashMap<>());
     }
 
     protected Map<String, String> getHeaders(final Map<String, String> overriddenHeaders) {
@@ -818,7 +821,7 @@
         }
     }
 
-    private final class Request<T extends Entity> {
+    private static final class Request<T extends Entity> {
         final String userChain;
         final String uri;
         final Revision revision;
@@ -1283,19 +1286,14 @@
 
     }
 
-    protected Response generateTokenResponse(ResponseBuilder builder, String token) {
-        // currently there is no way to use javax.servlet-api to set SameSite=Strict, so we do this using Jetty
-        HttpCookie jwtCookie = new HttpCookie(SecurityCookieName.AUTHORIZATION_BEARER.getName(), token, null, "/", VALID_FOR_SESSION_ONLY, true, true, null, 0, HttpCookie.SameSite.STRICT);
-        return builder.header(HttpHeader.SET_COOKIE.asString(), jwtCookie.getRFC6265SetCookie()).build();
-    }
-
-    protected void removeCookie(final HttpServletResponse httpServletResponse, final String cookieName) {
-        final Cookie cookie = new Cookie(cookieName, null);
-        cookie.setPath("/");
-        cookie.setHttpOnly(true);
-        cookie.setMaxAge(0);
-        cookie.setSecure(true);
-        httpServletResponse.addCookie(cookie);
+    /**
+     * Set Bearer Token as HTTP Session Cookie using standard Cookie Name
+     *
+     * @param response HTTP Servlet Response
+     * @param bearerToken JSON Web Token
+     */
+    protected void setBearerToken(final HttpServletResponse response, final String bearerToken) {
+        applicationCookieService.addSessionCookie(getCookieResourceUri(), response, ApplicationCookieName.AUTHORIZATION_BEARER, bearerToken);
     }
 
     protected String getNiFiUri() {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OIDCAccessResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OIDCAccessResource.java
index 475ee20..673e529 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OIDCAccessResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OIDCAccessResource.java
@@ -39,17 +39,15 @@
 import org.apache.nifi.authentication.exception.AuthenticationNotSupportedException;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
 import org.apache.nifi.util.NiFiProperties;
-import org.apache.nifi.web.security.http.SecurityCookieName;
+import org.apache.nifi.web.api.cookie.ApplicationCookieName;
 import org.apache.nifi.web.security.jwt.provider.BearerTokenProvider;
 import org.apache.nifi.web.security.oidc.OIDCEndpoints;
 import org.apache.nifi.web.security.oidc.OidcService;
 import org.apache.nifi.web.security.token.LoginAuthenticationToken;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.web.util.WebUtils;
 
 import javax.annotation.PreDestroy;
-import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.Consumes;
@@ -65,6 +63,7 @@
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import java.util.UUID;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -78,7 +77,6 @@
 public class OIDCAccessResource extends AccessResource {
 
     private static final Logger logger = LoggerFactory.getLogger(OIDCAccessResource.class);
-    private static final String OIDC_REQUEST_IDENTIFIER = "oidc-request-identifier";
     private static final String OIDC_ID_TOKEN_AUTHN_ERROR = "Unable to exchange authorization for ID token: ";
     private static final String OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED_MSG = "OpenId Connect support is not configured";
     private static final String REVOKE_ACCESS_TOKEN_LOGOUT = "oidc_access_token_logout";
@@ -144,12 +142,12 @@
     )
     public void oidcCallback(@Context HttpServletRequest httpServletRequest, @Context HttpServletResponse httpServletResponse) throws Exception {
         final AuthenticationResponse oidcResponse = parseOidcResponse(httpServletRequest, httpServletResponse, LOGGING_IN);
+        final Optional<String> requestIdentifier = getOidcRequestIdentifier();
 
-        final String oidcRequestIdentifier = WebUtils.getCookie(httpServletRequest, OIDC_REQUEST_IDENTIFIER).getValue();
-
-        if (oidcResponse != null && oidcResponse.indicatesSuccess()) {
+        if (requestIdentifier.isPresent() && oidcResponse != null && oidcResponse.indicatesSuccess()) {
             final AuthenticationSuccessResponse successfulOidcResponse = (AuthenticationSuccessResponse) oidcResponse;
 
+            final String oidcRequestIdentifier = requestIdentifier.get();
             checkOidcState(httpServletResponse, oidcRequestIdentifier, successfulOidcResponse, LOGGING_IN);
 
             try {
@@ -210,8 +208,8 @@
             return Response.status(Response.Status.CONFLICT).entity(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED_MSG).build();
         }
 
-        final String oidcRequestIdentifier = WebUtils.getCookie(httpServletRequest, OIDC_REQUEST_IDENTIFIER).getValue();
-        if (oidcRequestIdentifier == null) {
+        final Optional<String> requestIdentifier = getOidcRequestIdentifier();
+        if (!requestIdentifier.isPresent()) {
             final String message = "The login request identifier was not found in the request. Unable to continue.";
             logger.warn(message);
             return Response.status(Response.Status.BAD_REQUEST).entity(message).build();
@@ -221,12 +219,13 @@
         removeOidcRequestCookie(httpServletResponse);
 
         // get the jwt
-        final String jwt = oidcService.getJwt(oidcRequestIdentifier);
+        final String jwt = oidcService.getJwt(requestIdentifier.get());
         if (jwt == null) {
             throw new IllegalArgumentException("A JWT for this login request identifier could not be found. Unable to continue.");
         }
 
-        return generateTokenResponse(generateOkResponse(jwt), jwt);
+        setBearerToken(httpServletResponse, jwt);
+        return generateOkResponse(jwt).build();
     }
 
     @GET
@@ -247,7 +246,7 @@
         }
 
         final String mappedUserIdentity = NiFiUserUtils.getNiFiUserIdentity();
-        removeCookie(httpServletResponse, SecurityCookieName.AUTHORIZATION_BEARER.getName());
+        applicationCookieService.removeCookie(getCookieResourceUri(), httpServletResponse, ApplicationCookieName.AUTHORIZATION_BEARER);
         logger.debug("Invalidated JWT for user [{}]", mappedUserIdentity);
 
         // Get the oidc discovery url
@@ -291,13 +290,13 @@
     )
     public void oidcLogoutCallback(@Context HttpServletRequest httpServletRequest, @Context HttpServletResponse httpServletResponse) throws Exception {
         final AuthenticationResponse oidcResponse = parseOidcResponse(httpServletRequest, httpServletResponse, !LOGGING_IN);
+        final Optional<String> requestIdentifier = getOidcRequestIdentifier();
 
-        final String oidcRequestIdentifier = WebUtils.getCookie(httpServletRequest, OIDC_REQUEST_IDENTIFIER).getValue();
-
-        if (oidcResponse != null && oidcResponse.indicatesSuccess()) {
+        if (requestIdentifier.isPresent() && oidcResponse != null && oidcResponse.indicatesSuccess()) {
             final AuthenticationSuccessResponse successfulOidcResponse = (AuthenticationSuccessResponse) oidcResponse;
 
             // confirm state
+            final String oidcRequestIdentifier = requestIdentifier.get();
             checkOidcState(httpServletResponse, oidcRequestIdentifier, successfulOidcResponse, false);
 
             // Get the oidc discovery url
@@ -404,31 +403,16 @@
      * @return the authorization URI
      */
     private URI oidcRequestAuthorizationCode(@Context HttpServletResponse httpServletResponse, String callback) {
-
         final String oidcRequestIdentifier = UUID.randomUUID().toString();
-
-        // generate a cookie to associate this login sequence
-        final Cookie cookie = new Cookie(OIDC_REQUEST_IDENTIFIER, oidcRequestIdentifier);
-        cookie.setPath("/");
-        cookie.setHttpOnly(true);
-        cookie.setMaxAge(60);
-        cookie.setSecure(true);
-        httpServletResponse.addCookie(cookie);
-
-        // get the state for this request
+        applicationCookieService.addCookie(getCookieResourceUri(), httpServletResponse, ApplicationCookieName.OIDC_REQUEST_IDENTIFIER, oidcRequestIdentifier);
         final State state = oidcService.createState(oidcRequestIdentifier);
-
-        // build the authorization uri
-        final URI authorizationUri = UriBuilder.fromUri(oidcService.getAuthorizationEndpoint())
+        return UriBuilder.fromUri(oidcService.getAuthorizationEndpoint())
                 .queryParam("client_id", oidcService.getClientId())
                 .queryParam("response_type", "code")
                 .queryParam("scope", oidcService.getScope().toString())
                 .queryParam("state", state.getValue())
                 .queryParam("redirect_uri", callback)
                 .build();
-
-        // return Authorization URI
-        return authorizationUri;
     }
 
     private String determineLogoutMethod(String oidcDiscoveryUrl) {
@@ -475,7 +459,7 @@
     }
 
     @PreDestroy
-    private final void closeClient() throws IOException {
+    public void closeClient() throws IOException {
         httpClient.close();
     }
 
@@ -494,8 +478,8 @@
             return null;
         }
 
-        final String oidcRequestIdentifier = WebUtils.getCookie(httpServletRequest, OIDC_REQUEST_IDENTIFIER).getValue();
-        if (oidcRequestIdentifier == null) {
+        final Optional<String> requestIdentifier = getOidcRequestIdentifier();
+        if (!requestIdentifier.isPresent()) {
             forwardToMessagePage(httpServletRequest, httpServletResponse, pageTitle,"The request identifier was " +
                     "not found in the request. Unable to continue.");
             return null;
@@ -522,16 +506,12 @@
         // confirm state
         final State state = successfulOidcResponse.getState();
         if (state == null || !oidcService.isStateValid(oidcRequestIdentifier, state)) {
-            logger.error("The state value returned by the OpenId Connect Provider does not match the stored " +
-                    "state. Unable to continue login/logout process.");
+            logger.error("OIDC Request [{}] State [{}] not valid", oidcRequestIdentifier, state);
 
-            // remove the oidc request cookie
             removeOidcRequestCookie(httpServletResponse);
 
-            // forward to the error page
             forwardToMessagePage(httpServletRequest, httpServletResponse, getForwardPageTitle(isLogin), "Purposed state does not match " +
                     "the stored state. Unable to continue login/logout process.");
-            return;
         }
     }
 
@@ -552,7 +532,11 @@
     }
 
     private void removeOidcRequestCookie(final HttpServletResponse httpServletResponse) {
-        removeCookie(httpServletResponse, OIDC_REQUEST_IDENTIFIER);
+        applicationCookieService.removeCookie(getCookieResourceUri(), httpServletResponse, ApplicationCookieName.OIDC_REQUEST_IDENTIFIER);
+    }
+
+    private Optional<String> getOidcRequestIdentifier() {
+        return applicationCookieService.getCookieValue(httpServletRequest, ApplicationCookieName.OIDC_REQUEST_IDENTIFIER);
     }
 
     public void setOidcService(OidcService oidcService) {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SAMLAccessResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SAMLAccessResource.java
index 8d2439d..a374428 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SAMLAccessResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SAMLAccessResource.java
@@ -25,19 +25,17 @@
 import org.apache.nifi.authorization.util.IdentityMappingUtil;
 import org.apache.nifi.idp.IdpType;
 import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.api.cookie.ApplicationCookieName;
 import org.apache.nifi.web.security.logout.LogoutRequest;
 import org.apache.nifi.web.security.saml.SAMLCredentialStore;
 import org.apache.nifi.web.security.saml.SAMLEndpoints;
 import org.apache.nifi.web.security.saml.SAMLService;
 import org.apache.nifi.web.security.saml.SAMLStateManager;
 import org.apache.nifi.web.security.token.LoginAuthenticationToken;
-import org.opensaml.saml2.metadata.provider.MetadataProviderException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.security.saml.SAMLCredential;
-import org.springframework.web.util.WebUtils;
 
-import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.Consumes;
@@ -53,6 +51,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.UUID;
 import java.util.stream.Collectors;
@@ -65,7 +64,6 @@
 public class SAMLAccessResource extends AccessResource {
 
     private static final Logger logger = LoggerFactory.getLogger(SAMLAccessResource.class);
-    private static final String SAML_REQUEST_IDENTIFIER = "saml-request-identifier";
     private static final String SAML_METADATA_MEDIA_TYPE = "application/samlmetadata+xml";
     private static final String LOGOUT_REQUEST_IDENTIFIER_NOT_FOUND = "The logout request identifier was not found in the request. Unable to continue.";
     private static final String LOGOUT_REQUEST_NOT_FOUND_FOR_GIVEN_IDENTIFIER = "No logout request was found for the given identifier. Unable to continue.";
@@ -84,7 +82,7 @@
             value = "Retrieves the service provider metadata.",
             notes = NON_GUARANTEED_ENDPOINT
     )
-    public Response samlMetadata(@Context HttpServletRequest httpServletRequest, @Context HttpServletResponse httpServletResponse) throws Exception {
+    public Response samlMetadata(@Context HttpServletRequest httpServletRequest, @Context HttpServletResponse httpServletResponse) {
         // only consider user specific access over https
         if (!httpServletRequest.isSecure()) {
             throw new AuthenticationNotSupportedException(AUTHENTICATION_NOT_ENABLED_MSG);
@@ -122,12 +120,7 @@
         final String samlRequestIdentifier = UUID.randomUUID().toString();
 
         // generate a cookie to associate this login sequence
-        final Cookie cookie = new Cookie(SAML_REQUEST_IDENTIFIER, samlRequestIdentifier);
-        cookie.setPath("/");
-        cookie.setHttpOnly(true);
-        cookie.setMaxAge(60);
-        cookie.setSecure(true);
-        httpServletResponse.addCookie(cookie);
+        applicationCookieService.addCookie(getCookieResourceUri(), httpServletResponse, ApplicationCookieName.SAML_REQUEST_IDENTIFIER, samlRequestIdentifier);
 
         // get the state for this request
         final String relayState = samlStateManager.createState(samlRequestIdentifier);
@@ -137,7 +130,6 @@
             samlService.initiateLogin(httpServletRequest, httpServletResponse, relayState);
         } catch (Exception e) {
             forwardToLoginMessagePage(httpServletRequest, httpServletResponse, e.getMessage());
-            return;
         }
     }
 
@@ -184,8 +176,8 @@
         initializeSamlServiceProvider();
 
         // ensure the request has the cookie with the request id
-        final String samlRequestIdentifier = WebUtils.getCookie(httpServletRequest, SAML_REQUEST_IDENTIFIER).getValue();
-        if (samlRequestIdentifier == null) {
+        final Optional<String> requestIdentifier = getSamlRequestIdentifier();
+        if (!requestIdentifier.isPresent()) {
             forwardToLoginMessagePage(httpServletRequest, httpServletResponse, "The login request identifier was not found in the request. Unable to continue.");
             return;
         }
@@ -199,6 +191,7 @@
         }
 
         // ensure the RelayState value in the request matches the store state
+        final String samlRequestIdentifier = requestIdentifier.get();
         if (!samlStateManager.isStateValid(samlRequestIdentifier, requestState)) {
             logger.error("The RelayState value returned by the SAML IDP does not match the stored state. Unable to continue login process.");
             removeSamlRequestCookie(httpServletResponse);
@@ -258,7 +251,7 @@
             notes = NON_GUARANTEED_ENDPOINT
     )
     public Response samlLoginExchange(@Context HttpServletRequest httpServletRequest,
-                                      @Context HttpServletResponse httpServletResponse) throws Exception {
+                                      @Context HttpServletResponse httpServletResponse) {
 
         // only consider user specific access over https
         if (!httpServletRequest.isSecure()) {
@@ -271,30 +264,26 @@
             return Response.status(Response.Status.CONFLICT).entity(SAMLService.SAML_SUPPORT_IS_NOT_CONFIGURED).build();
         }
 
-        logger.info("Attempting to exchange SAML login request for a NiFi JWT...");
-
         // ensure saml service provider is initialized
         initializeSamlServiceProvider();
 
         // ensure the request has the cookie with the request identifier
-        final String samlRequestIdentifier = WebUtils.getCookie(httpServletRequest, SAML_REQUEST_IDENTIFIER).getValue();
-        if (samlRequestIdentifier == null) {
+        final Optional<String> requestIdentifier = getSamlRequestIdentifier();
+        if (!requestIdentifier.isPresent()) {
             final String message = "The login request identifier was not found in the request. Unable to continue.";
             logger.warn(message);
             return Response.status(Response.Status.BAD_REQUEST).entity(message).build();
         }
 
-        // remove the saml request cookie
         removeSamlRequestCookie(httpServletResponse);
-
-        // get the jwt
+        final String samlRequestIdentifier = requestIdentifier.get();
         final String jwt = samlStateManager.getJwt(samlRequestIdentifier);
         if (jwt == null) {
             throw new IllegalArgumentException("A JWT for this login request identifier could not be found. Unable to continue.");
         }
 
-        // generate the response
-        logger.info("SAML login exchange complete");
+        logger.info("SAML Login Request [{}] Completed", samlRequestIdentifier);
+        setBearerToken(httpServletResponse, jwt);
         return generateOkResponse(jwt).build();
     }
 
@@ -312,13 +301,14 @@
         assert(isSamlEnabled(httpServletRequest, httpServletResponse, !LOGGING_IN));
 
         // ensure the logout request identifier is present
-        final String logoutRequestIdentifier = WebUtils.getCookie(httpServletRequest, LOGOUT_REQUEST_IDENTIFIER).getValue();
-        if (StringUtils.isBlank(logoutRequestIdentifier)) {
+        final Optional<String> cookieValue = getLogoutRequestIdentifier();
+        if (!cookieValue.isPresent()) {
             forwardToLogoutMessagePage(httpServletRequest, httpServletResponse, LOGOUT_REQUEST_IDENTIFIER_NOT_FOUND);
             return;
         }
 
         // ensure there is a logout request in progress for the given identifier
+        final String logoutRequestIdentifier = cookieValue.get();
         final LogoutRequest logoutRequest = logoutRequestManager.get(logoutRequestIdentifier);
         if (logoutRequest == null) {
             forwardToLogoutMessagePage(httpServletRequest, httpServletResponse, LOGOUT_REQUEST_NOT_FOUND_FOR_GIVEN_IDENTIFIER);
@@ -341,9 +331,8 @@
         try {
             logger.info("Initiating SAML Single Logout with IDP...");
             samlService.initiateLogout(httpServletRequest, httpServletResponse, samlCredential);
-        } catch (Exception e) {
+        } catch (final Exception e) {
             forwardToLogoutMessagePage(httpServletRequest, httpServletResponse, e.getMessage());
-            return;
         }
     }
 
@@ -400,13 +389,14 @@
         initializeSamlServiceProvider();
 
         // ensure the logout request identifier is present
-        final String logoutRequestIdentifier = WebUtils.getCookie(httpServletRequest, LOGOUT_REQUEST_IDENTIFIER).getValue();
-        if (StringUtils.isBlank(logoutRequestIdentifier)) {
+        final Optional<String> requestIdentifier = getLogoutRequestIdentifier();
+        if (!requestIdentifier.isPresent()) {
             forwardToLogoutMessagePage(httpServletRequest, httpServletResponse, LOGOUT_REQUEST_IDENTIFIER_NOT_FOUND);
             return;
         }
 
         // ensure there is a logout request in progress for the given identifier
+        final String logoutRequestIdentifier = requestIdentifier.get();
         final LogoutRequest logoutRequest = logoutRequestManager.get(logoutRequestIdentifier);
         if (logoutRequest == null) {
             forwardToLogoutMessagePage(httpServletRequest, httpServletResponse, LOGOUT_REQUEST_NOT_FOUND_FOR_GIVEN_IDENTIFIER);
@@ -473,7 +463,7 @@
         httpServletResponse.sendRedirect(getNiFiLogoutCompleteUri());
     }
 
-    private void initializeSamlServiceProvider() throws MetadataProviderException {
+    private void initializeSamlServiceProvider() {
         if (!samlService.isServiceProviderInitialized()) {
             final String samlMetadataUri = generateResourceUri("saml", "metadata");
             final String baseUri = samlMetadataUri.replace("/saml/metadata", "");
@@ -490,7 +480,7 @@
     }
 
     private void removeSamlRequestCookie(final HttpServletResponse httpServletResponse) {
-        removeCookie(httpServletResponse, SAML_REQUEST_IDENTIFIER);
+        applicationCookieService.removeCookie(getCookieResourceUri(), httpServletResponse, ApplicationCookieName.SAML_REQUEST_IDENTIFIER);
     }
 
     private boolean isSamlEnabled(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse, boolean isLogin) throws Exception {
@@ -514,6 +504,10 @@
         return isLogin ? ApplicationResource.LOGIN_ERROR_TITLE : ApplicationResource.LOGOUT_ERROR_TITLE;
     }
 
+    private Optional<String> getSamlRequestIdentifier() {
+        return applicationCookieService.getCookieValue(httpServletRequest, ApplicationCookieName.SAML_REQUEST_IDENTIFIER);
+    }
+
     public void setSamlService(SAMLService samlService) {
         this.samlService = samlService;
     }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/cookie/ApplicationCookieName.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/cookie/ApplicationCookieName.java
new file mode 100644
index 0000000..cb665eb
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/cookie/ApplicationCookieName.java
@@ -0,0 +1,42 @@
+/*
+ * 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.nifi.web.api.cookie;
+
+import org.apache.nifi.web.security.http.SecurityCookieName;
+
+/**
+ * Application Cookie Names
+ */
+public enum ApplicationCookieName {
+    AUTHORIZATION_BEARER(SecurityCookieName.AUTHORIZATION_BEARER.getName()),
+
+    LOGOUT_REQUEST_IDENTIFIER("nifi-logout-request-identifier"),
+
+    OIDC_REQUEST_IDENTIFIER("nifi-oidc-request-identifier"),
+
+    SAML_REQUEST_IDENTIFIER("nifi-saml-request-identifier");
+
+    private final String cookieName;
+
+    ApplicationCookieName(final String cookieName) {
+        this.cookieName = cookieName;
+    }
+
+    public String getCookieName() {
+        return cookieName;
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/cookie/ApplicationCookieService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/cookie/ApplicationCookieService.java
new file mode 100644
index 0000000..fabcc29
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/cookie/ApplicationCookieService.java
@@ -0,0 +1,65 @@
+/*
+ * 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.nifi.web.api.cookie;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.net.URI;
+import java.util.Optional;
+
+/**
+ * Application Cookie Service capable of generating and retrieving HTTP Cookies using standard properties
+ */
+public interface ApplicationCookieService {
+    /**
+     * Generate cookie with specified value
+     *
+     * @param resourceUri Resource URI containing path and domain
+     * @param response HTTP Servlet Response
+     * @param applicationCookieName Application Cookie Name to be added
+     * @param value Cookie value to be added
+     */
+    void addCookie(URI resourceUri, HttpServletResponse response, ApplicationCookieName applicationCookieName, String value);
+
+    /**
+     * Generate cookie with session-based expiration and specified value
+     *
+     * @param resourceUri Resource URI containing path and domain
+     * @param response HTTP Servlet Response
+     * @param applicationCookieName Application Cookie Name
+     * @param value Cookie value to be added
+     */
+    void addSessionCookie(URI resourceUri, HttpServletResponse response, ApplicationCookieName applicationCookieName, String value);
+
+    /**
+     * Get cookie value using specified name
+     *
+     * @param request HTTP Servlet Response
+     * @param applicationCookieName Application Cookie Name to be retrieved
+     * @return Optional Cookie Value
+     */
+    Optional<String> getCookieValue(HttpServletRequest request, ApplicationCookieName applicationCookieName);
+
+    /**
+     * Generate cookie with an empty value instructing the client to remove the cookie
+     *
+     * @param resourceUri Resource URI containing path and domain
+     * @param response HTTP Servlet Response
+     * @param applicationCookieName Application Cookie Name to be removed
+     */
+    void removeCookie(URI resourceUri, HttpServletResponse response, ApplicationCookieName applicationCookieName);
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/cookie/StandardApplicationCookieService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/cookie/StandardApplicationCookieService.java
new file mode 100644
index 0000000..41267d3
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/cookie/StandardApplicationCookieService.java
@@ -0,0 +1,134 @@
+/*
+ * 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.nifi.web.api.cookie;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.ResponseCookie;
+import org.springframework.web.util.WebUtils;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.HttpHeaders;
+import java.net.URI;
+import java.time.Duration;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Standard implementation of Application Cookie Service using Spring Framework utilities
+ */
+public class StandardApplicationCookieService implements ApplicationCookieService {
+    private static final Duration MAX_AGE_SESSION = Duration.ofSeconds(-1);
+
+    private static final Duration MAX_AGE_REMOVE = Duration.ZERO;
+
+    private static final Duration MAX_AGE_STANDARD = Duration.ofSeconds(60);
+
+    private static final String DEFAULT_PATH = "/";
+
+    private static final String SAME_SITE_STRICT = "Strict";
+
+    private static final boolean SECURE_ENABLED = true;
+
+    private static final boolean HTTP_ONLY_ENABLED = true;
+
+    private static final Logger logger = LoggerFactory.getLogger(StandardApplicationCookieService.class);
+
+    /**
+     * Generate cookie with specified value
+     *
+     * @param resourceUri Resource URI containing path and domain
+     * @param response HTTP Servlet Response
+     * @param applicationCookieName Application Cookie Name to be added
+     * @param value Cookie value to be added
+     */
+    @Override
+    public void addCookie(final URI resourceUri, final HttpServletResponse response, final ApplicationCookieName applicationCookieName, final String value) {
+        final ResponseCookie.ResponseCookieBuilder responseCookieBuilder = getCookieBuilder(resourceUri, applicationCookieName, value, MAX_AGE_STANDARD);
+        setResponseCookie(response, responseCookieBuilder.build());
+        logger.debug("Added Cookie [{}] URI [{}]", applicationCookieName.getCookieName(), resourceUri);
+    }
+
+    /**
+     * Generate cookie with session-based expiration and specified value as well as SameSite Strict property
+     *
+     * @param resourceUri Resource URI containing path and domain
+     * @param response HTTP Servlet Response
+     * @param applicationCookieName Application Cookie Name
+     * @param value Cookie value to be added
+     */
+    @Override
+    public void addSessionCookie(final URI resourceUri, final HttpServletResponse response, final ApplicationCookieName applicationCookieName, final String value) {
+        final ResponseCookie.ResponseCookieBuilder responseCookieBuilder = getCookieBuilder(resourceUri, applicationCookieName, value, MAX_AGE_SESSION);
+        responseCookieBuilder.sameSite(SAME_SITE_STRICT);
+        setResponseCookie(response, responseCookieBuilder.build());
+        logger.debug("Added Session Cookie [{}] URI [{}]", applicationCookieName.getCookieName(), resourceUri);
+    }
+
+    /**
+     * Get cookie value using specified name
+     *
+     * @param request HTTP Servlet Response
+     * @param applicationCookieName Application Cookie Name to be retrieved
+     * @return Optional Cookie Value
+     */
+    @Override
+    public Optional<String> getCookieValue(final HttpServletRequest request, final ApplicationCookieName applicationCookieName) {
+        final Cookie cookie = WebUtils.getCookie(request, applicationCookieName.getCookieName());
+        return cookie == null ? Optional.empty() : Optional.of(cookie.getValue());
+    }
+
+    /**
+     * Generate cookie with an empty value instructing the client to remove the cookie with a maximum age of 60 seconds
+     *
+     * @param resourceUri Resource URI containing path and domain
+     * @param response HTTP Servlet Response
+     * @param applicationCookieName Application Cookie Name to be removed
+     */
+    @Override
+    public void removeCookie(final URI resourceUri, final HttpServletResponse response, final ApplicationCookieName applicationCookieName) {
+        Objects.requireNonNull(response, "Response required");
+        final ResponseCookie.ResponseCookieBuilder responseCookieBuilder = getCookieBuilder(resourceUri, applicationCookieName, StringUtils.EMPTY, MAX_AGE_REMOVE);
+        setResponseCookie(response, responseCookieBuilder.build());
+        logger.debug("Removed Cookie [{}] URI [{}]", applicationCookieName.getCookieName(), resourceUri);
+    }
+
+    private ResponseCookie.ResponseCookieBuilder getCookieBuilder(final URI resourceUri,
+                                             final ApplicationCookieName applicationCookieName,
+                                             final String value,
+                                             final Duration maxAge) {
+        Objects.requireNonNull(resourceUri, "Resource URI required");
+        Objects.requireNonNull(applicationCookieName, "Response Cookie Name required");
+        return ResponseCookie.from(applicationCookieName.getCookieName(), value)
+                .path(getCookiePath(resourceUri))
+                .domain(resourceUri.getHost())
+                .secure(SECURE_ENABLED)
+                .httpOnly(HTTP_ONLY_ENABLED)
+                .maxAge(maxAge);
+    }
+
+    private void setResponseCookie(final HttpServletResponse response, final ResponseCookie responseCookie) {
+        response.addHeader(HttpHeaders.SET_COOKIE, responseCookie.toString());
+    }
+
+    private String getCookiePath(final URI resourceUri) {
+        return StringUtils.defaultIfBlank(resourceUri.getPath(), DEFAULT_PATH);
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestDataTransferResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestDataTransferResource.java
index 84573b0..f503395 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestDataTransferResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestDataTransferResource.java
@@ -147,6 +147,7 @@
         final URI locationUri = new URI(locationUriStr);
         doReturn(uriBuilder).when(uriInfo).getBaseUriBuilder();
         doReturn(uriBuilder).when(uriBuilder).path(any(String.class));
+        doReturn(uriBuilder).when(uriBuilder).segment(any(String.class));
         doReturn(locationUri).when(uriBuilder).build();
         return uriInfo;
     }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/cookie/StandardApplicationCookieServiceTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/cookie/StandardApplicationCookieServiceTest.java
new file mode 100644
index 0000000..0889107
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/cookie/StandardApplicationCookieServiceTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.nifi.web.api.cookie;
+
+import org.apache.nifi.util.StringUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.mock.web.MockCookie;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.HttpHeaders;
+import java.net.URI;
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class StandardApplicationCookieServiceTest {
+    private static final String DOMAIN = "localhost.localdomain";
+
+    private static final String RESOURCE_URI = String.format("https://%s", DOMAIN);
+
+    private static final String ROOT_PATH = "/";
+
+    private static final String CONTEXT_PATH = "/context";
+
+    private static final String CONTEXT_RESOURCE_URI = String.format("https://%s%s", DOMAIN, CONTEXT_PATH);
+
+    private static final int EXPECTED_MAX_AGE = 60;
+
+    private static final int SESSION_MAX_AGE = -1;
+
+    private static final int REMOVE_MAX_AGE = 0;
+
+    private static final String SAME_SITE_STRICT = "SameSite=Strict";
+
+    private static final String COOKIE_VALUE = UUID.randomUUID().toString();
+
+    private static final ApplicationCookieName COOKIE_NAME = ApplicationCookieName.LOGOUT_REQUEST_IDENTIFIER;
+
+    private URI resourceUri;
+
+    private URI contextResourceUri;
+
+    private StandardApplicationCookieService service;
+
+    @Mock
+    private HttpServletRequest request;
+
+    @Mock
+    private HttpServletResponse response;
+
+    @Captor
+    private ArgumentCaptor<String> cookieArgumentCaptor;
+
+    @Before
+    public void setService() {
+        service = new StandardApplicationCookieService();
+        resourceUri = URI.create(RESOURCE_URI);
+        contextResourceUri = URI.create(CONTEXT_RESOURCE_URI);
+    }
+
+    @Test
+    public void testAddCookie() {
+        service.addCookie(resourceUri, response, COOKIE_NAME, COOKIE_VALUE);
+
+        verify(response).addHeader(eq(HttpHeaders.SET_COOKIE), cookieArgumentCaptor.capture());
+        final String setCookieHeader = cookieArgumentCaptor.getValue();
+        assertAddCookieMatches(setCookieHeader, ROOT_PATH, EXPECTED_MAX_AGE);
+    }
+
+    @Test
+    public void testAddCookieContextPath() {
+        service.addCookie(contextResourceUri, response, COOKIE_NAME, COOKIE_VALUE);
+
+        verify(response).addHeader(eq(HttpHeaders.SET_COOKIE), cookieArgumentCaptor.capture());
+        final String setCookieHeader = cookieArgumentCaptor.getValue();
+        assertAddCookieMatches(setCookieHeader, CONTEXT_PATH, EXPECTED_MAX_AGE);
+    }
+
+    @Test
+    public void testAddSessionCookie() {
+        service.addSessionCookie(resourceUri, response, COOKIE_NAME, COOKIE_VALUE);
+
+        verify(response).addHeader(eq(HttpHeaders.SET_COOKIE), cookieArgumentCaptor.capture());
+
+        final String setCookieHeader = cookieArgumentCaptor.getValue();
+        assertAddCookieMatches(setCookieHeader, ROOT_PATH, SESSION_MAX_AGE);
+        assertTrue("SameSite not found", setCookieHeader.endsWith(SAME_SITE_STRICT));
+    }
+
+    @Test
+    public void testAddSessionCookieContextPath() {
+        service.addSessionCookie(contextResourceUri, response, COOKIE_NAME, COOKIE_VALUE);
+
+        verify(response).addHeader(eq(HttpHeaders.SET_COOKIE), cookieArgumentCaptor.capture());
+
+        final String setCookieHeader = cookieArgumentCaptor.getValue();
+        assertAddCookieMatches(setCookieHeader, CONTEXT_PATH, SESSION_MAX_AGE);
+        assertTrue("SameSite not found", setCookieHeader.endsWith(SAME_SITE_STRICT));
+    }
+
+    @Test
+    public void testGetCookieValue() {
+        final Cookie cookie = new Cookie(COOKIE_NAME.getCookieName(), COOKIE_VALUE);
+        when(request.getCookies()).thenReturn(new Cookie[]{cookie});
+        final Optional<String> cookieValue = service.getCookieValue(request, COOKIE_NAME);
+        assertTrue(cookieValue.isPresent());
+        assertEquals(COOKIE_VALUE, cookieValue.get());
+    }
+
+    @Test
+    public void testGetCookieValueEmpty() {
+        final Optional<String> cookieValue = service.getCookieValue(request, COOKIE_NAME);
+        assertFalse(cookieValue.isPresent());
+    }
+
+    @Test
+    public void testRemoveCookie() {
+        service.removeCookie(resourceUri, response, COOKIE_NAME);
+
+        verify(response).addHeader(eq(HttpHeaders.SET_COOKIE), cookieArgumentCaptor.capture());
+        final String setCookieHeader = cookieArgumentCaptor.getValue();
+        assertRemoveCookieMatches(setCookieHeader, ROOT_PATH);
+    }
+
+    @Test
+    public void testRemoveCookieContextPath() {
+        service.removeCookie(contextResourceUri, response, COOKIE_NAME);
+
+        verify(response).addHeader(eq(HttpHeaders.SET_COOKIE), cookieArgumentCaptor.capture());
+        final String setCookieHeader = cookieArgumentCaptor.getValue();
+        assertRemoveCookieMatches(setCookieHeader, CONTEXT_PATH);
+    }
+
+    private void assertAddCookieMatches(final String setCookieHeader, final String path, final long maxAge) {
+        final Cookie cookie = MockCookie.parse(setCookieHeader);
+        assertCookieMatches(setCookieHeader, cookie, path);
+        assertEquals(COOKIE_VALUE, cookie.getValue());
+        assertEquals(maxAge, cookie.getMaxAge());
+    }
+
+    private void assertRemoveCookieMatches(final String setCookieHeader, final String path) {
+        final Cookie cookie = MockCookie.parse(setCookieHeader);
+        assertCookieMatches(setCookieHeader, cookie, path);
+        assertEquals(StringUtils.EMPTY, cookie.getValue());
+        assertEquals(REMOVE_MAX_AGE, cookie.getMaxAge());
+    }
+
+    private void assertCookieMatches(final String setCookieHeader, final Cookie cookie, final String path) {
+        assertEquals("Cookie Name not matched", COOKIE_NAME.getCookieName(), cookie.getName());
+        assertEquals("Path not matched", path, cookie.getPath());
+        assertEquals("Domain not matched", DOMAIN, cookie.getDomain());
+        assertTrue("HTTP Only not matched", cookie.isHttpOnly());
+        assertTrue("Secure not matched", cookie.getSecure());
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/http/SecurityCookieName.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/http/SecurityCookieName.java
index 96689c4..9e54c29 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/http/SecurityCookieName.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/http/SecurityCookieName.java
@@ -20,7 +20,8 @@
  * Enumeration of HTTP Cookie Names for Security
  */
 public enum SecurityCookieName {
-    AUTHORIZATION_BEARER("__Host-Authorization-Bearer");
+    /** See IETF Cookie Prefixes Draft Section 3.1 related to Secure prefix handling */
+    AUTHORIZATION_BEARER("__Secure-Authorization-Bearer");
 
     private String name;
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/message-page.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/message-page.jsp
index 9999ec0..2ba6ae4 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/message-page.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/message-page.jsp
@@ -40,7 +40,7 @@
                 }).on('mouseleave', function () {
                     $(this).removeClass('link-over');
                 }).on('click', function () {
-                    window.location = '<%= contextPath %>/nifi';
+                    window.location = '<%= contextPath %>/nifi/';
                 });
             });
         </script>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
index 22f1a73..d1cc08c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js
@@ -116,9 +116,9 @@
                 if (accessStatus.status === 'ACTIVE') {
                     // reload as appropriate - no need to schedule token refresh as the page is reloading
                     if (top !== window) {
-                        parent.window.location = '/nifi';
+                        parent.window.location = '../nifi/';
                     } else {
-                        window.location = '/nifi';
+                        window.location = '../nifi/';
                     }
                 } else {
                     $('#login-message-title').text('Unable to log in');
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/logout/nf-logout.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/logout/nf-logout.js
index 0259a23..d353c26 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/logout/nf-logout.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/logout/nf-logout.js
@@ -38,7 +38,7 @@
         }).on('mouseleave', function () {
             $(this).removeClass('link-over');
         }).on('click', function () {
-            window.location = '../nifi';
+            window.location = '../nifi/';
         });
     });
 }));
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
index 41a36e9..a884f55 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
@@ -110,9 +110,9 @@
         // handle home
         $('#user-home').on('click', function () {
             if (top !== window) {
-                parent.window.location = '../nifi';
+                parent.window.location = '../nifi/';
             } else {
-                window.location = '../nifi';
+                window.location = '../nifi/';
             }
         });
     });
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-error-handler.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-error-handler.js
index ff0e56f..583b666 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-error-handler.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-error-handler.js
@@ -58,7 +58,7 @@
                         headerText: 'Session Expired',
                         dialogContent: 'Your session has expired. Please press Ok to log in again.',
                         okHandler: function () {
-                            window.location = '/nifi';
+                            window.location = '../nifi/';
                         }
                     });
                 }