Skip to content

Commit 7d77c10

Browse files
committed
Fix HX-Redirect to respect servlet context path
When server.servlet.context-path is set (e.g. /app), the HX-Redirect header and loginUrl JSON field now correctly include the context path (e.g. /app/user/login.html) matching LoginUrlAuthenticationEntryPoint behavior. Found by Codex review.
1 parent 5749a79 commit 7d77c10

2 files changed

Lines changed: 66 additions & 2 deletions

File tree

src/main/java/com/digitalsanctuary/spring/user/security/HtmxAwareAuthenticationEntryPoint.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,16 @@ public void commence(HttpServletRequest request, HttpServletResponse response,
5757
return;
5858
}
5959

60+
// Prepend the servlet context path so deployments with server.servlet.context-path work correctly.
61+
// LoginUrlAuthenticationEntryPoint does the same when building its redirect URL.
62+
String contextPath = request.getContextPath();
63+
String fullLoginUrl = contextPath + loginUrl;
64+
6065
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
6166
response.setCharacterEncoding("UTF-8");
6267
response.setContentType("application/json;charset=UTF-8");
63-
response.setHeader(HX_REDIRECT_HEADER, loginUrl);
64-
String escapedLoginUrl = loginUrl
68+
response.setHeader(HX_REDIRECT_HEADER, fullLoginUrl);
69+
String escapedLoginUrl = fullLoginUrl
6570
.replace("\\", "\\\\")
6671
.replace("\"", "\\\"")
6772
.replace("\n", "\\n")

src/test/java/com/digitalsanctuary/spring/user/security/HtmxAwareAuthenticationEntryPointTest.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class HtmxRequestHandling {
5959
@BeforeEach
6060
void setUp() throws IOException {
6161
when(request.getHeader("HX-Request")).thenReturn("true");
62+
when(request.getContextPath()).thenReturn("");
6263
responseBody = new StringWriter();
6364
when(response.getWriter()).thenReturn(new PrintWriter(responseBody));
6465
}
@@ -137,6 +138,7 @@ void shouldNotCallDelegateWhenHtmxRequestReceived() throws IOException, ServletE
137138
void shouldHandleHxRequestHeaderCaseInsensitively() throws IOException, ServletException {
138139
// Given
139140
when(request.getHeader("HX-Request")).thenReturn("TRUE");
141+
when(request.getContextPath()).thenReturn("");
140142
AuthenticationException authException = new InsufficientAuthenticationException("Session expired");
141143

142144
// When
@@ -148,6 +150,63 @@ void shouldHandleHxRequestHeaderCaseInsensitively() throws IOException, ServletE
148150
}
149151
}
150152

153+
@Nested
154+
@DisplayName("Servlet Context Path Handling")
155+
class ServletContextPathHandling {
156+
157+
private StringWriter responseBody;
158+
159+
@BeforeEach
160+
void setUp() throws IOException {
161+
when(request.getHeader("HX-Request")).thenReturn("true");
162+
responseBody = new StringWriter();
163+
when(response.getWriter()).thenReturn(new PrintWriter(responseBody));
164+
}
165+
166+
@Test
167+
@DisplayName("Should prepend context path to HX-Redirect header when context path is non-empty")
168+
void shouldPrependContextPathToHxRedirectWhenContextPathIsNonEmpty() throws IOException, ServletException {
169+
// Given
170+
when(request.getContextPath()).thenReturn("/app");
171+
AuthenticationException authException = new InsufficientAuthenticationException("Session expired");
172+
173+
// When
174+
entryPoint.commence(request, response, authException);
175+
176+
// Then
177+
verify(response).setHeader("HX-Redirect", "/app" + LOGIN_URL);
178+
}
179+
180+
@Test
181+
@DisplayName("Should include context path in JSON loginUrl when context path is non-empty")
182+
void shouldIncludeContextPathInJsonLoginUrlWhenContextPathIsNonEmpty() throws IOException, ServletException {
183+
// Given
184+
when(request.getContextPath()).thenReturn("/app");
185+
AuthenticationException authException = new InsufficientAuthenticationException("Session expired");
186+
187+
// When
188+
entryPoint.commence(request, response, authException);
189+
190+
// Then
191+
assertThat(responseBody.toString()).contains("\"loginUrl\":\"/app" + LOGIN_URL + "\"");
192+
}
193+
194+
@Test
195+
@DisplayName("Should use login URL as-is when context path is empty")
196+
void shouldUseLoginUrlAsIsWhenContextPathIsEmpty() throws IOException, ServletException {
197+
// Given
198+
when(request.getContextPath()).thenReturn("");
199+
AuthenticationException authException = new InsufficientAuthenticationException("Session expired");
200+
201+
// When
202+
entryPoint.commence(request, response, authException);
203+
204+
// Then
205+
verify(response).setHeader("HX-Redirect", LOGIN_URL);
206+
assertThat(responseBody.toString()).contains("\"loginUrl\":\"" + LOGIN_URL + "\"");
207+
}
208+
}
209+
151210
@Nested
152211
@DisplayName("Non-HTMX Request Handling")
153212
class NonHtmxRequestHandling {

0 commit comments

Comments
 (0)