Skip to content

Commit 885b9a4

Browse files
committed
Add tests
1 parent ddab328 commit 885b9a4

2 files changed

Lines changed: 92 additions & 6 deletions

File tree

src/main/java/com/auth0/StorageUtils.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,6 @@ private StorageUtils() {}
1212
static final String NONCE_KEY = "com.auth0.nonce";
1313
static final String ORIGIN_DOMAIN_KEY = "com.auth0.origin_domain";
1414

15-
/**
16-
* Max-Age for transaction cookies in seconds (10 minutes).
17-
* Orphaned cookies from abandoned login flows will auto-expire.
18-
*/
19-
static final int TRANSACTION_COOKIE_MAX_AGE = 600;
20-
2115
/**
2216
* Constructs a transaction-keyed state cookie name.
2317
* Each login transaction gets its own cookie, preventing multi-tab overwrites.

src/test/java/com/auth0/TransientCookieStoreTest.java

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,98 @@ public void shouldNotDeleteOtherTransactionCookies() {
343343
assertThat(deletedCookies[0].getMaxAge(), is(0));
344344
}
345345

346+
// --- Nonce multi-tab isolation tests ---
347+
348+
@Test
349+
public void shouldIsolateMultipleNonceTransactionsOnRetrieval() {
350+
// Both nonce cookies exist (two concurrent tabs)
351+
Cookie nonceA = new Cookie("com.auth0.nonce.stateA", "nonceA");
352+
Cookie nonceB = new Cookie("com.auth0.nonce.stateB", "nonceB");
353+
request.setCookies(nonceA, nonceB);
354+
355+
// Retrieve nonce for Tab A — should get nonceA without touching nonceB
356+
String resultA = TransientCookieStore.getNonce(request, response, "stateA");
357+
assertThat(resultA, is("nonceA"));
358+
359+
// Only stateA's nonce cookie should be deleted
360+
Cookie[] deletedCookies = response.getCookies();
361+
assertThat(deletedCookies.length, is(1));
362+
assertThat(deletedCookies[0].getName(), is("com.auth0.nonce.stateA"));
363+
assertThat(deletedCookies[0].getMaxAge(), is(0));
364+
365+
// Tab B can still retrieve its nonce independently (new request)
366+
MockHttpServletRequest requestB = new MockHttpServletRequest();
367+
MockHttpServletResponse responseB = new MockHttpServletResponse();
368+
requestB.setCookies(nonceA, nonceB);
369+
370+
String resultB = TransientCookieStore.getNonce(requestB, responseB, "stateB");
371+
assertThat(resultB, is("nonceB"));
372+
}
373+
374+
@Test
375+
public void shouldPreferTransactionKeyedNonceOverLegacyAndNotDeleteLegacy() {
376+
Cookie txCookie = new Cookie("com.auth0.nonce.stateA", "txNonce");
377+
Cookie legacyCookie = new Cookie("com.auth0.nonce", "legacyNonce");
378+
request.setCookies(txCookie, legacyCookie);
379+
380+
String nonce = TransientCookieStore.getNonce(request, response, "stateA");
381+
assertThat(nonce, is("txNonce"));
382+
383+
// Transaction-keyed cookie was consumed; legacy cookie should NOT be deleted
384+
// (it may belong to another in-flight flow upgrading from v1)
385+
Cookie[] deletedCookies = response.getCookies();
386+
assertThat(deletedCookies.length, is(1));
387+
assertThat(deletedCookies[0].getName(), is("com.auth0.nonce.stateA"));
388+
}
389+
390+
@Test
391+
public void shouldRetrieveCorrectStateAndNonceForEachTabEndToEnd() {
392+
// Simulate two concurrent login flows storing state + nonce
393+
MockHttpServletResponse storeResponse = new MockHttpServletResponse();
394+
TransientCookieStore.storeState(storeResponse, "stateA", SameSite.LAX, false, false, null);
395+
TransientCookieStore.storeNonce(storeResponse, "nonceA", "stateA", SameSite.LAX, false, false, null);
396+
TransientCookieStore.storeState(storeResponse, "stateB", SameSite.LAX, false, false, null);
397+
TransientCookieStore.storeNonce(storeResponse, "nonceB", "stateB", SameSite.LAX, false, false, null);
398+
399+
// Tab A callback
400+
MockHttpServletRequest requestA = new MockHttpServletRequest();
401+
MockHttpServletResponse responseA = new MockHttpServletResponse();
402+
requestA.setCookies(
403+
new Cookie("com.auth0.state.stateA", "stateA"),
404+
new Cookie("com.auth0.nonce.stateA", "nonceA"),
405+
new Cookie("com.auth0.state.stateB", "stateB"),
406+
new Cookie("com.auth0.nonce.stateB", "nonceB")
407+
);
408+
requestA.setParameter("state", "stateA");
409+
410+
String stateA = TransientCookieStore.getState(requestA, responseA);
411+
String nonceA = TransientCookieStore.getNonce(requestA, responseA, "stateA");
412+
assertThat(stateA, is("stateA"));
413+
assertThat(nonceA, is("nonceA"));
414+
415+
// Tab B callback
416+
MockHttpServletRequest requestB = new MockHttpServletRequest();
417+
MockHttpServletResponse responseB = new MockHttpServletResponse();
418+
requestB.setCookies(
419+
new Cookie("com.auth0.state.stateA", "stateA"),
420+
new Cookie("com.auth0.nonce.stateA", "nonceA"),
421+
new Cookie("com.auth0.state.stateB", "stateB"),
422+
new Cookie("com.auth0.nonce.stateB", "nonceB")
423+
);
424+
requestB.setParameter("state", "stateB");
425+
426+
String stateB = TransientCookieStore.getState(requestB, responseB);
427+
String nonceB = TransientCookieStore.getNonce(requestB, responseB, "stateB");
428+
assertThat(stateB, is("stateB"));
429+
assertThat(nonceB, is("nonceB"));
430+
431+
// Verify Tab A's retrieval didn't affect Tab B's cookies
432+
Cookie[] deletedByA = responseA.getCookies();
433+
for (Cookie c : deletedByA) {
434+
assertThat(c.getName(), not(containsString("stateB")));
435+
}
436+
}
437+
346438
// --- Origin domain tests ---
347439

348440
private static final String TEST_SECRET = "testClientSecret123";

0 commit comments

Comments
 (0)