Level AAA

When a session expires mid-transaction, the partially-completed work must survive the re-authentication round trip -- the server-side draft keyed to the user identity, or encrypted in the re-auth payload, so the user returns to the same step with the same data

2.2.5 Re-authenticating

In Plain Language

2.2.5 Re-authenticating (Level AAA) says that when an authenticated session expires mid-activity, the user can continue after re-authenticating without loss of data[1]. The partially-completed work -- form fields, multi-step wizard position, comment draft, shopping cart, document edits -- has to survive the auth round trip.

Mechanism: the application persists in-progress state somewhere that outlives the session (server-side draft keyed to the user, encrypted payload round-tripped through the re-auth page, or client-side storage) and rehydrates it after the login succeeds. The spec's sufficient techniques name exactly two paths: server-side draft storage (G105) and encrypted hidden-field encoding (G181)[1].

Why It Matters

  • Users with cognitive and learning disabilities work through multi-step forms at a pace the session timer did not account for. When the 30-minute idle timer fires on step 14 of a 20-step benefits application, a re-auth flow that drops the user at step 1 of an empty form is not a minor inconvenience -- it is a hard stop on the task.
  • Screen-reader users traverse forms linearly and verify each field against its accessible name before committing, so a session that times out during composition costs more re-entry time than it would for a sighted mouse user.
  • Switch users, eye-tracker users, and users typing with head pointers operate at input rates measured in characters per minute, not per second. A timeout that discards input penalises exactly the users 2.2.5 exists to protect.
  • 2.2.5 sits next to 2.2.1 Timing Adjustable: 2.2.1 gives the user a way to extend or disable the timer, 2.2.5 is the backstop for when the timer fires anyway. Shipping one without the other leaves a data-loss gap.

Examples

Do: Persist a server-side draft keyed to user identity and rehydrate after re-auth

Session expired during form entry:

1. User fills steps 1-3 of a 5-step form

2. Session times out -- server saves draft

3. User re-authenticates

4. Application restores form at step 3

✔ All data preserved across re-authentication

<!-- Periodic server-side draft save, keyed to user id -->
<script>
  const form = document.getElementById('app-form');
  setInterval(() => {
    const body = new FormData(form);
    body.append('draft_id', form.dataset.draftId);
    fetch('/api/drafts', { method: 'POST', body, credentials: 'same-origin' });
  }, 30000);
</script>

<!-- On re-auth success, server looks up the draft by
     (user_id, draft_id) and redirects back to the form
     with the saved state rehydrated -->
Do: Round-trip an encrypted state blob through the re-auth page

Re-login form carries the encrypted return state:

<input type="hidden" name="return_to" value="/wizard/step-3">

<input type="hidden" name="state" value="enc:AES-GCM:...">

✔ After login, server decrypts the state and rehydrates the wizard

<!-- Re-auth page carries the pending state as an
     encrypted, integrity-protected blob (G181) -->
<form action="/login" method="POST">
  <input type="hidden" name="return_to" value="/wizard/step-3">
  <input type="hidden" name="state"
         value="enc:AES-GCM:9f2a...e4b1">
  <label for="username">Username</label>
  <input type="text" id="username" name="username" autocomplete="username">
  <label for="password">Password</label>
  <input type="password" id="password" name="password" autocomplete="current-password">
  <button type="submit">Sign in</button>
</form>
Don't: Redirect to the home page after re-auth and discard the in-progress state

Session expired -- all form data lost:

1. User fills steps 1-3 of a 5-step form

2. Session times out

3. User re-authenticates

4. Redirected to home page -- form data gone

✘ User must restart the entire form from scratch

Don't: Return a "session expired" error with no draft and no return URL

Error page on session expiry:

<h1>Session Expired</h1>

<p>Your session has timed out.</p>

<a href="/login">Log in again</a>

✘ No draft saved, no return URL -- all user data is permanently lost

How to Fix It

  1. Persist drafts server-side, keyed to user identity plus a draft id. On a periodic interval (every 20-30 seconds during active editing) and on every meaningful state change (step transition, field blur on a long textarea), POST the current state to a drafts endpoint. Key the record by (user_id, draft_id) so the re-auth handshake can look it up without a live session. This is the W3C sufficient technique G105[1].
  2. Or round-trip the state through the re-auth page as an encrypted blob. Serialize the pending state, encrypt with an integrity-protected construction (AES-GCM, not raw AES-CBC), drop it into a hidden field on the login form, and decrypt it on successful authentication. This is W3C sufficient technique G181 and is the fallback when server-side draft storage is not available[1].
  3. Detect impending session expiry and prompt re-auth in place. At roughly 90% of the inactivity timeout, open a modal that re-authenticates against the same origin (fetch to /auth/refresh, or a silent OAuth refresh) without unmounting the underlying page. The DOM state -- form fields, scroll position, open accordions -- stays live because the page never navigates. Pair this with 2.2.1 Timing Adjustable so the warning itself offers an extend-session action.
  4. Use client-side storage as a belt-and-braces fallback. IndexedDB with a periodic save (or sessionStorage for short flows) catches the cases where the network save failed before the timeout. Restore from client storage on page load when a matching draft id is present in the URL. Client storage is not a substitute for server-side drafts -- it does not survive a device change -- but it closes the tab-crash gap.
  5. Exercise the full timeout-and-recovery cycle in tests. Force the session to expire (clear the session cookie, or wait out the timer on a shortened test config), re-authenticate, and assert that every field -- including file uploads, radio selections, contenteditable regions, and scroll position -- is restored. A manual walkthrough on the longest form in the product catches the regressions automated tests miss.

References

  1. [1] W3C (2023). Understanding Success Criterion 2.2.5: Re-authenticating. W3C, Accessed 2026-04-07. https://www.w3.org/WAI/WCAG22/Understanding/re-authenticating.html