@@ -13,25 +13,40 @@ interface OktaConfig {
1313}
1414
1515/**
16- * How long a successful Okta session check is trusted before we hit the
17- * network again. `transformAuthState` runs on EVERY auth-state recalculation
18- * (page load, route-driven re-render, token renewal, cross-tab storage sync),
19- * and `session.exists()` is a network call that returns `false` on ANY
20- * failure — network blip, rate limit (429), aborted request — not just when
21- * the Okta session is genuinely gone. Without this cache, a burst of
22- * refreshes/route changes (multiplied across open tabs by syncStorage events)
23- * hammers /api/v1/sessions/me, and a single transient failure instantly flips
24- * isAuthenticated to false and bounces an active user to the login page with
25- * no timeout warning.
16+ * How long one successful Okta session check is trusted before we verify
17+ * against the server again.
18+ *
19+ * Why this cache exists: `transformAuthState` runs on EVERY auth-state
20+ * recalculation — page load, token renewal, and (because `syncStorage` is on)
21+ * every token event mirrored from other tabs. Each run used to make a network
22+ * call to /api/v1/sessions/me, and `session.exists()` reports `false` for ANY
23+ * failure (network blip, 429 rate limit, aborted request), not just a dead
24+ * session. So a burst of refreshes/route changes could hammer that endpoint
25+ * and a single transient failure logged an active user out with no warning.
26+ * Trusting a recent positive result removes both the call volume and the
27+ * false-logout window.
2628 */
2729export const SESSION_CHECK_TTL_MS = 5 * 60 * 1000 ; // 5 minutes
2830let lastSessionConfirmedAt = 0 ;
2931
30- /** Test-only: clears the session-check cache between test cases. */
32+ /**
33+ * Test-only: module state survives between test cases, so tests reset the
34+ * session-check cache here to keep each case independent.
35+ */
3136export const resetSessionCheckCache = ( ) : void => {
3237 lastSessionConfirmedAt = 0 ;
3338} ;
3439
40+ /**
41+ * Extra auth check layered on top of Okta's default (unexpired tokens exist).
42+ *
43+ * Why: tokens sitting in localStorage don't guarantee the user still has a
44+ * live Okta SSO session (it may have been revoked or timed out server-side).
45+ * Okta calls this hook every time it recalculates auth state; we confirm the
46+ * server-side session before treating the user as authenticated — but only
47+ * once per SESSION_CHECK_TTL_MS, and with one retry, so transient network
48+ * failures can't end a valid session.
49+ */
3550export const transformAuthState = async ( oktaAuth , authState ) => {
3651 // verifies unexpired tokens are available from the tokenManager (default behavior)
3752 if ( localStorage . getItem ( "madieDebug" ) || ( window as any ) . madieDebug ) {
@@ -126,7 +141,7 @@ function OktaSecurity() {
126141 // offline_access depends on the Okta/HARP app allowing refresh tokens and
127142 // is being decided separately.
128143 tokenManager : {
129- autoRenew : true , // renew tokens
144+ autoRenew : true , // expired tokens are renewed, not removed
130145 storage : "localStorage" , // required for cross-tab token sync
131146 } ,
132147 services : {
0 commit comments