WCAG 2.2.5: Re-authenticating
You spend forty minutes on a form. The session expires. You log back in — and everything is gone. That experience is exactly what this criterion forbids. Sessions may expire, but re-authenticating must return the user to their activity with their data intact. Security keeps its timeouts; users keep their work.
The success criterion, in full
When an authenticated session expires, the user can continue the activity without loss of data after re-authenticating.
Note the framing: the criterion does not restrict when or why sessions expire. It restricts what expiry is allowed to cost the user — nothing.
Who this helps
Session lengths are calibrated to an imagined average user. Real users routinely take longer:
Screen reader users
A long form is traversed field by field, with each label, hint, and error read aloud. Twenty-minute sessions expire mid-form as a matter of routine.
People with motor disabilities
Switch access or on-screen keyboards can multiply input time by ten. Re-entering lost data is not an annoyance — it is a physically expensive ordeal.
People with cognitive disabilities
Reading assistance, breaks, and double-checking take time. Fear of the session clock adds anxiety that makes errors more likely.
Anyone who gets interrupted
Caregivers, workers on call, people on shared or public machines with enforced short sessions. Life happens mid-task.
When lost work must be redone, the cost lands hardest on the users for whom the work was slowest — a perfectly regressive penalty. 2.2.5 removes it.
Implementation patterns
The W3C documents both server-side and client-side routes. Most robust products combine them.
1. Server-side: park the submission, re-authenticate, resume
When a request arrives with an expired session, do not discard the payload. Store it temporarily (or round-trip it as hidden encoded data), send the user through login, then complete the original action or re-display the form pre-filled. This is W3C techniques G105 (saving data so it can be used after re-authentication) and G181 (encoding user data in the re-authorization flow).
2. Client-side: re-authenticate without unloading the page
Detect expiry and open the login step in a dialog or new tab while the original page — and everything typed into it — stays alive. After login succeeds, retry the pending request. The user may never even perceive an interruption.
3. Continuous auto-save of drafts
Persist work-in-progress every few seconds — to the server keyed to the account, or locally as a fallback. On the next authenticated visit, restore the draft and tell the user. This pattern also satisfies the data-preservation route of 2.2.6 Timeouts if drafts are kept for more than 20 hours.
Pass and fail examples
✓ Passes 2.2.5
- A webmail client that keeps the half-written email in the compose window while the user re-logs-in via a dialog, then sends it.
- A government benefits application that auto-saves each section; after re-authentication the user lands on the section they left.
- A checkout that detects an expired session on submit, stores the order payload, and completes the order right after login.
- A CMS that restores an unsaved draft — with a visible “draft restored” notice — when the author signs back in.
✗ Fails 2.2.5
- Submitting a long form after expiry redirects to the login page and the payload is discarded.
- Logging back in always lands on the dashboard; the multi-step wizard restarts at step one.
- A survey platform that invalidates all answered questions when the session token lapses.
- An expiry dialog whose only option is “Log in again” and which reloads the page, wiping the unsaved editor content.
Code examples
Retry the failed request after in-place re-authentication
Intercept the 401, hold the payload, re-authenticate in a dialog, and replay — the page, and the user’s work, never unloads.
async function submitWithReauth(url, payload) {
let res = await fetch(url, {
method: "POST",
body: JSON.stringify(payload),
headers: { "Content-Type": "application/json" },
});
if (res.status === 401) {
// Session expired: data stays in memory, page stays put.
await openLoginDialog(); // accessible modal; resolves on success
res = await fetch(url, {
method: "POST",
body: JSON.stringify(payload), // ✓ same payload, nothing lost
headers: { "Content-Type": "application/json" },
});
}
return res;
}Auto-save and restore a draft
Continuous draft persistence makes session expiry (and crashes, and accidental tabs closed) a non-event. Tell the user when a draft is restored.
const form = document.querySelector("#application-form");
const DRAFT_KEY = `draft:${form.dataset.formId}`;
// Save on every change, debounced
let t;
form.addEventListener("input", () => {
clearTimeout(t);
t = setTimeout(() => {
const data = Object.fromEntries(new FormData(form));
localStorage.setItem(DRAFT_KEY, JSON.stringify(data));
// Also POST to /drafts when the session is alive, so the
// draft follows the account across devices.
}, 800);
});
// Restore after (re-)login
const draft = localStorage.getItem(DRAFT_KEY);
if (draft) {
for (const [name, value] of Object.entries(JSON.parse(draft))) {
const field = form.elements.namedItem(name);
if (field) field.value = value;
}
announce("We restored your unsaved draft from your last visit.");
}Server-side: park the payload through the login round-trip
// Express-style sketch of W3C technique G105
app.post("/applications", (req, res) => {
if (!req.session.user) {
// ✓ Park the submission instead of discarding it
const parkId = crypto.randomUUID();
parkedSubmissions.set(parkId, { body: req.body, expires: in20Hours() });
return res.redirect(`/login?resume=${parkId}`);
}
saveApplication(req.session.user, req.body);
});
app.post("/login", async (req, res) => {
const user = await authenticate(req.body);
const parked = parkedSubmissions.get(req.query.resume);
if (parked) {
saveApplication(user, parked.body); // ✓ complete the original action
return res.redirect("/applications/confirmation");
}
res.redirect("/dashboard");
});Common failures
- Redirecting an expired-session POST to the login page and dropping the request body — the single most common failure mode.
- Full-page reload after re-login that discards unsent client state (rich-text editors, multi-step wizards, file selections).
- Restoring the session but not the place: the user is authenticated again but dumped at the home page with the wizard reset.
- Auto-save that only runs on explicit 'Save' — the criterion is about the data present when expiry strikes, which is precisely the unsaved part.
- Draft restoration that exists but is silent, so users assume the work is gone and start over anyway.
- CSRF token rotation on re-login that invalidates the held form, producing a cryptic error that costs the data after all.
- Preserving data on desktop flows but not in the places sessions are shortest — banking-style apps, kiosks, and shared-terminal modes.
How to test for 2.2.5
- 1
Force an expiry mid-task
Start every significant authenticated activity — long form, editor, checkout, wizard — then kill the session (wait it out, delete the cookie, or invalidate it server-side). This is the whole test setup; the next steps are observations.
- 2
Try to continue via the normal path
Interact with the page or submit. Follow whatever re-authentication flow appears, exactly as a user would. No developer tools, no back-button tricks.
- 3
Verify the data survived
After logging back in, check every field, selection, uploaded file, and step position. 'Mostly restored' is a failure — the criterion says without loss of data.
- 4
Verify the activity continues
You should resume where you were: same wizard step, same editor state, or the parked action completed automatically. Landing authenticated-but-lost on a dashboard fails the 'continue the activity' clause.
- 5
Repeat with assistive technology
Run one pass with a screen reader: the expiry notice must be announced, the login dialog must be operable, and the restoration message must be perceivable — silent recovery helps nobody who cannot see it.
This criterion is untestable by automated scanners — it lives entirely in application behavior. Fold it into QA scenarios and the full WCAG 2.2 checklist.
Frequently asked questions
What does WCAG 2.2.5 Re-authenticating require?
It requires that when an authenticated session expires, the user can continue the activity without loss of data after re-authenticating. Sessions are allowed to expire — security policies are untouched — but expiry must not destroy the user's work. If someone spends forty minutes writing a support request, gets logged out, and signs back in, their draft must still be there and the flow must continue where it left off.
Does 2.2.5 prevent me from using session timeouts?
No. The criterion explicitly accommodates security-driven session expiry; it regulates the consequence, not the timeout. You may end sessions as aggressively as your security model demands, provided the user's in-progress data survives the round trip through the login screen. This is what makes 2.2.5 compatible with banking, healthcare, and government applications that require short sessions.
How do sites typically preserve data across re-authentication?
Two W3C-documented approaches: (1) server-side — encode or temporarily store the submitted data when the server detects the expired session, run the user through re-authentication, then complete or re-display the original submission (techniques G105 and G181); (2) client-side — keep the work in the browser (unsent form state, local drafts) and re-authenticate in a separate tab, overlay, or iframe so the original page never unloads. Auto-saving drafts continuously is the most robust pattern because it also covers crashes and accidental navigation.
What about sensitive data that should not be stored?
The W3C guidance acknowledges this: where data must not be retained (say, payment details on a shared terminal), re-encountering the form is acceptable if the data could not legitimately be saved — but the design should still minimize loss, for example by preserving the non-sensitive fields and clearly telling users what they will need to re-enter. The spirit of the criterion is that users never silently lose work they could reasonably expect to be kept.
How does 2.2.5 relate to 2.2.1 Timing Adjustable and 2.2.6 Timeouts?
They cover three moments of the same lifecycle. 2.2.1 (Level A) requires that time limits, including session limits, can be turned off, adjusted, or extended before they fire — though it has an 'essential' exception that security timeouts often use. 2.2.6 (AAA) requires warning users up front how much inactivity causes data loss. 2.2.5 (AAA) handles the aftermath: once expiry happens, re-login must restore the user's context and data. A well-built app satisfies all three with one architecture: warn, extend, auto-save, restore.
Who benefits most from 2.2.5?
Anyone who works slower than the session clock: screen reader users navigating long forms linearly, people with motor disabilities for whom each field takes longer to complete, people with cognitive disabilities who need pauses or re-reads, and people who are simply interrupted — a parent, a nurse on shift, anyone on a shared computer that enforces short sessions. Losing thirty minutes of effort is discouraging for anyone; for users for whom that effort was physically expensive, it can mean abandoning the task permanently.
Related Success Criteria
Users can turn off, adjust, or extend time limits.
Moving, blinking, or auto-updating content can be paused, stopped, or hidden.
Timing is not an essential part of the event or activity.
Interruptions can be postponed or suppressed by the user.
Users are warned of the duration of inactivity that could cause data loss.