WCAG 2.2.5: Ponowne uwierzytelnianie
Wstęp do WCAG 2.2.5: Ponowne uwierzytelnianie
Kryterium sukcesu WCAG 2.2.5, „Ponowne uwierzytelnianie” (Re-authenticating), wprowadzone w WCAG 2.2, dotyczy sytuacji, w której użytkownik jest zmuszony do ponownego uwierzytelnienia się po wygaśnięciu sesji. Jego głównym celem jest zapewnienie, że wszelkie dane wprowadzone przez użytkownika lub jego stan interakcji z aplikacją zostaną przywrócone po pomyślnym ponownym zalogowaniu. Zapobiega to utracie pracy i frustracji użytkownika, co jest kluczowe dla dostępności i użyteczności.
W praktyce oznacza to, że jeśli użytkownik wypełniał formularz, edytował dokument lub konfigurował ustawienia, a jego sesja wygasła, po ponownym zalogowaniu powinien wrócić do stanu, w jakim znajdował się przed wygaśnięciem sesji, a wszystkie wprowadzone dane powinny być zachowane.
Dlaczego to kryterium jest ważne? (Wpływ na dostępność)
Utrata danych lub postępu pracy po ponownym uwierzytelnieniu może być niezwykle frustrująca i stanowić poważną barierę dla wielu grup użytkowników. Zgodność z tym kryterium ma znaczący wpływ na:
- Użytkowników z niepełnosprawnościami poznawczymi: Osoby z problemami z pamięcią krótkotrwałą, koncentracją uwagi lub funkcjami wykonawczymi mogą mieć trudności z zapamiętaniem i ponownym wprowadzeniem utraconych danych. Wymóg ponownego wykonania już wykonanej pracy może być dla nich zniechęcający lub wręcz niemożliwy.
- Użytkowników z niepełnosprawnościami ruchowymi: Ponowne wprowadzanie dużych ilości danych lub ponowne wykonywanie złożonych sekwencji działań jest czasochłonne i może być fizycznie wyczerpujące dla osób korzystających z alternatywnych metod wprowadzania danych (np. myszy sterowanej ustami, przełączników).
- Użytkowników z niepełnosprawnościami wzroku: Po utracie danych, odnalezienie miejsca, w którym praca została przerwana, i ponowne wprowadzenie informacji może być skomplikowane i wymagać zwiększonego wysiłku percepcyjnego przy użyciu czytnika ekranu.
- Każdego użytkownika: Niezależnie od niepełnosprawności, każdy użytkownik może doświadczyć przerwy w pracy (np. telefon, rozproszenie, awaria sprzętu). Utrata danych po wygaśnięciu sesji i konieczności ponownego uwierzytelnienia prowadzi do irytacji, straty czasu i obniżenia ogólnej użyteczności systemu. Zapewnienie ciągłości pracy podnosi satysfakcję i efektywność.
Kryteria sukcesu i wymagania WCAG 2.2.5
Oficjalne sformułowanie kryterium sukcesu 2.2.5 na poziomie AA (z WCAG 2.2) brzmi:
2.2.5 Ponowne uwierzytelnianie (Re-authenticating):
Gdy sesja uwierzytelniona wygaśnie, a użytkownik pomyślnie ponownie się uwierzytelni, wszystkie dane wprowadzone przez użytkownika w bieżącej sesji powinny zostać przywrócone.
Kluczowe elementy do zrozumienia:
- „Sesja uwierzytelniona wygaśnie”: Odnosi się to do scenariuszy, w których użytkownik jest zalogowany do systemu, a następnie jego sesja wygasa z powodu braku aktywności lub upływu czasu, co wymaga ponownego zalogowania (np. poprzez wprowadzenie hasła).
- „użytkownik pomyślnie ponownie się uwierzytelni”: Oznacza to, że użytkownik przeszedł proces ponownego logowania i został ponownie zautoryzowany w systemie.
- „wszystkie dane wprowadzone przez użytkownika w bieżącej sesji powinny zostać przywrócone”: Jest to najważniejsza część kryterium. Obejmuje to:
- Treść pól formularza (tekst, liczby, wybrane opcje).
- Stan interfejsu użytkownika (np. filtry, sortowanie tabel, rozwijane sekcje, postęp w kreatorze wieloetapowym).
- Niezapisane zmiany w edytorach tekstu lub innych narzędziach do tworzenia treści.
Celem jest, aby użytkownik po ponownym zalogowaniu znalazł się w tym samym kontekście i z tymi samymi danymi, w których był przed wygaśnięciem sesji.
Praktyczne wytyczne do zgodności
Aby spełnić kryterium 2.2.5, należy wdrożyć mechanizmy, które w bezpieczny sposób przechowują i przywracają stan użytkownika. Oto kluczowe aspekty:
1. Zarządzanie stanem po stronie serwera
To najbezpieczniejsza i zalecana metoda, szczególnie dla danych wrażliwych lub krytycznych. Gdy sesja wygasa:
- Zapisz dane tymczasowo: Aplikacja powinna tymczasowo zapisać dane wprowadzone przez użytkownika (np. zawartość formularza, bieżący etap procesu) w bezpiecznej, powiązanej z użytkownikiem pamięci po stronie serwera. Dane te powinny być powiązane z unikalnym identyfikatorem sesji lub użytkownika, a nie bezpośrednio z tokenem autoryzacji.
- Szyfrowanie: Wszystkie tymczasowo przechowywane dane powinny być szyfrowane.
- Po ponownym uwierzytelnieniu: Po udanym zalogowaniu, system powinien pobrać tymczasowo zapisane dane i automatycznie wypełnić nimi odpowiednie pola lub przywrócić stan aplikacji, a następnie usunąć tymczasowe dane.
- Przekierowanie: Użytkownik powinien zostać przekierowany na stronę lub do widoku, w którym znajdował się przed wygaśnięciem sesji.
2. Zarządzanie stanem po stronie klienta (z ostrożnością)
Dla mniej wrażliwych danych, takich jak filtry, sortowanie, stan przewijania czy niewielkie ilości danych tekstowych, można rozważyć przechowywanie ich po stronie klienta.
localStorage
lubsessionStorage
: Mogą być używane do przechowywania stanu, który nie jest wrażliwy.sessionStorage
jest czyszczone po zamknięciu zakładki/okna,localStorage
utrzymuje się dłużej.- Ograniczenia bezpieczeństwa: Należy unikać przechowywania poufnych informacji (haseł, numerów kart kredytowych, danych osobowych) bezpośrednio w
localStorage
, ponieważ są one łatwo dostępne dla skryptów po stronie klienta i mogą być podatne na ataki XSS. - Synchronizacja z serwerem: W idealnym scenariuszu, dane z
localStorage
byłyby synchronizowane z serwerem po ponownym uwierzytelnieniu, aby zapewnić spójność i bezpieczeństwo.
3. Proces ponownego uwierzytelnienia
- Kiedy sesja wygaśnie, użytkownik powinien być jasno poinformowany o konieczności ponownego zalogowania.
- Po pomyślnym zalogowaniu, użytkownik powinien zostać przekierowany na tę samą stronę, z której został „wylogowany”, z przywróconymi danymi.
Przykłady implementacji
Poprawna implementacja (z wykorzystaniem symulacji serwera i localStorage dla prostoty)
Poniższy przykład demonstruje, jak można zachować dane wprowadzone w polu tekstowym po wygaśnięciu sesji i ponownym uwierzytelnieniu. W rzeczywistej aplikacji dane te byłyby przesyłane na serwer, a nie przechowywane w localStorage
, szczególnie jeśli są wrażliwe.
HTML:
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WCAG 2.2.5 - Przykład Poprawny</title>
<style>
body { font-family: sans-serif; margin: 20px; }
textarea { width: 100%; height: 150px; padding: 10px; border: 1px solid #ccc; }
button { padding: 10px 15px; background-color: #007bff; color: white; border: none; cursor: pointer; margin-top: 10px; }
.message { margin-top: 10px; padding: 10px; border: 1px solid #d4edda; background-color: #d4edda; color: #155724; display: none; }
.error { border: 1px solid #f8d7da; background-color: #f8d7da; color: #721c24; display: none; }
</style>
</head>
<body>
<h1>Poprawna implementacja WCAG 2.2.5</h1>
<p>Wprowadź tekst poniżej. Sesja wygaśnie za 15 sekund.</p>
<form id="myForm">
<label for="content">Twoja treść:</label><br>
<textarea id="content" name="content" placeholder="Wpisz tutaj swój tekst..."></textarea><br>
<button type="submit">Zapisz (symulacja)</button>
</form>
<button id="simulateLogout">Symuluj wygaśnięcie sesji / Wyloguj</button>
<div id="statusMessage" class="message">Dane przywrócone!</div>
<div id="errorMessage" class="error">Sesja wygasła! Proszę zalogować się ponownie.</div>
<div id="loginForm" style="display: none; border: 1px solid #ccc; padding: 20px; margin-top: 20px; background-color: #f9f9f9;">
<h3>Logowanie</h3>
<p>Wprowadź dowolną nazwę użytkownika i hasło (np. "test" / "test")</p>
<label for="username">Użytkownik:</label><br>
<input type="text" id="username" name="username" value="test"><br><br>
<label for="password">Hasło:</label><br>
<input type="password" id="password" name="password" value="test"><br><br>
<button id="loginButton">Zaloguj się</button>
</div>
<script>
// Symulacja stanu sesji i danych użytkownika
let isLoggedIn = true;
let sessionTimeout;
const contentTextarea = document.getElementById('content');
const myForm = document.getElementById('myForm');
const simulateLogoutButton = document.getElementById('simulateLogout');
const loginForm = document.getElementById('loginForm');
const loginButton = document.getElementById('loginButton');
const statusMessage = document.getElementById('statusMessage');
const errorMessage = document.getElementById('errorMessage');
function startSessionTimer() {
clearTimeout(sessionTimeout);
sessionTimeout = setTimeout(() => {
if (isLoggedIn) {
logoutUser(true); // true means session expired
}
}, 15000); // Sesja wygasa po 15 sekundach braku aktywności (w tym przykładzie po 15s od załadowania)
}
function saveDraft() {
localStorage.setItem('draftContent', contentTextarea.value);
console.log('Draft saved to localStorage.');
}
function restoreDraft() {
const savedContent = localStorage.getItem('draftContent');
if (savedContent) {
contentTextarea.value = savedContent;
statusMessage.style.display = 'block';
setTimeout(() => statusMessage.style.display = 'none', 3000);
console.log('Draft restored from localStorage.');
}
}
function loginUser() {
// W rzeczywistości byłaby tu walidacja danych logowania z serwerem
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
if (username === 'test' && password === 'test') {
isLoggedIn = true;
loginForm.style.display = 'none';
myForm.style.display = 'block';
simulateLogoutButton.style.display = 'block';
errorMessage.style.display = 'none';
restoreDraft();
startSessionTimer(); // Restart timer po zalogowaniu
console.log('User logged in.');
} else {
alert('Błędna nazwa użytkownika lub hasło.');
}
}
function logoutUser(sessionExpired = false) {
isLoggedIn = false;
saveDraft(); // Zapisz dane przed wylogowaniem
clearTimeout(sessionTimeout);
myForm.style.display = 'none';
simulateLogoutButton.style.display = 'none';
loginForm.style.display = 'block';
if (sessionExpired) {
errorMessage.style.display = 'block';
}
console.log('User logged out. Session expired:', sessionExpired);
}
// Zdarzenia
contentTextarea.addEventListener('input', saveDraft); // Zapisuj draft na bieżąco
myForm.addEventListener('submit', (e) => {
e.preventDefault();
alert('Dane zostały "zapisane" (symulacja wysłania na serwer).');
localStorage.removeItem('draftContent'); // Czyść draft po "zapisie"
console.log('Form submitted. Draft cleared.');
startSessionTimer(); // Resetuj timer po aktywności
});
simulateLogoutButton.addEventListener('click', () => logoutUser(false));
loginButton.addEventListener('click', loginUser);
// Inicjalizacja
if (isLoggedIn) {
restoreDraft(); // Próbuj przywrócić dane przy pierwszym załadowaniu
startSessionTimer();
} else {
myForm.style.display = 'none';
simulateLogoutButton.style.display = 'none';
loginForm.style.display = 'block';
}
</script>
</body>
</html>
Niepoprawna implementacja
W tym przypadku, po wygaśnięciu sesji i ponownym uwierzytelnieniu, wszystkie wprowadzone dane zostają utracone, co jest niezgodne z WCAG 2.2.5.
HTML:
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WCAG 2.2.5 - Przykład Niepoprawny</title>
<style>
body { font-family: sans-serif; margin: 20px; }
textarea { width: 100%; height: 150px; padding: 10px; border: 1px solid #ccc; }
button { padding: 10px 15px; background-color: #dc3545; color: white; border: none; cursor: pointer; margin-top: 10px; }
.message { margin-top: 10px; padding: 10px; border: 1px solid #d4edda; background-color: #d4edda; color: #155724; display: none; }
.error { border: 1px solid #f8d7da; background-color: #f8d7da; color: #721c24; }
</style>
</head>
<body>
<h1>Niepoprawna implementacja WCAG 2.2.5</h1>
<p>Wprowadź tekst poniżej. Symulacja wygaśnięcia sesji spowoduje utratę danych.</p>
<form id="myForm">
<label for="content">Twoja treść:</label><br>
<textarea id="content" name="content" placeholder="Wpisz tutaj swój tekst..."></textarea><br>
<button type="submit">Zapisz</button>
</form>
<button id="simulateLogout">Symuluj wygaśnięcie sesji / Wyloguj</button>
<div id="loginForm" style="display: none; border: 1px solid #ccc; padding: 20px; margin-top: 20px; background-color: #f9f9f9;">
<h3>Logowanie</h3>
<p class="error">Sesja wygasła! Proszę zalogować się ponownie. Wprowadzone dane zostały utracone.</p>
<label for="username">Użytkownik:</label><br>
<input type="text" id="username" name="username" value="test"><br><br>
<label for="password">Hasło:</label><br>
<input type="password" id="password" name="password" value="test"><br><br>
<button id="loginButton">Zaloguj się</button>
</div>
<script>
let isLoggedIn = true;
const contentTextarea = document.getElementById('content');
const myForm = document.getElementById('myForm');
const simulateLogoutButton = document.getElementById('simulateLogout');
const loginForm = document.getElementById('loginForm');
const loginButton = document.getElementById('loginButton');
function loginUser() {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
if (username === 'test' && password === 'test') {
isLoggedIn = true;
loginForm.style.display = 'none';
myForm.style.display = 'block';
simulateLogoutButton.style.display = 'block';
// TUTAJ BRAK PRZYWRACANIA DANYCH
console.log('User logged in. Data lost.');
} else {
alert('Błędna nazwa użytkownika lub hasło.');
}
}
function logoutUser() {
isLoggedIn = false;
myForm.style.display = 'none';
simulateLogoutButton.style.display = 'none';
loginForm.style.display = 'block';
contentTextarea.value = ''; // Wyczyść pole po wylogowaniu (symulacja utraty danych)
console.log('User logged out. Data lost.');
}
myForm.addEventListener('submit', (e) => {
e.preventDefault();
alert('Dane zostały "zapisane" (symulacja wysłania na serwer).');
// W tej symulacji dane są po prostu "tracone" po wylogowaniu
});
simulateLogoutButton.addEventListener('click', logoutUser);
loginButton.addEventListener('click', loginUser);
// Inicjalizacja
if (!isLoggedIn) {
myForm.style.display = 'none';
simulateLogoutButton.style.display = 'none';
loginForm.style.display = 'block';
}
</script>
</body>
</html>
Najlepsze praktyki i typowe pułapki
Najlepsze praktyki:
- Priorytetyzacja danych: Zidentyfikuj, które dane są kluczowe do zachowania (np. niezapisane dokumenty, postęp w formularzach wieloetapowych) i skup się na ich bezpiecznym przywracaniu.
- Zarządzanie stanem po stronie serwera: Dla danych wrażliwych i dla zapewnienia najwyższej niezawodności, zawsze preferuj przechowywanie stanu użytkownika po stronie serwera. Wykorzystuj unikalne identyfikatory sesji do powiązania danych z użytkownikiem.
- Płynne doświadczenie użytkownika: Zaprojektuj proces ponownego uwierzytelniania tak, aby był jak najbardziej bezproblemowy i intuicyjny. Unikaj konieczności ponownego nawigowania.
- Jasne komunikaty: Jeśli sesja wygasła, poinformuj użytkownika o tym fakcie i o tym, że jego dane zostały zachowane, jeśli tak jest.
- Testowanie: Dokładnie testuj scenariusze wygaśnięcia sesji i ponownego uwierzytelniania na różnych urządzeniach i w różnych przeglądarkach.
- Szyfrowanie: Zawsze szyfruj dane użytkownika przechowywane tymczasowo, zarówno po stronie serwera, jak i klienta (jeśli używasz localStorage dla nietrwałych, nie-wrażliwych danych).
Typowe pułapki:
- Brak przywracania stanu formularzy: Często zapomina się o przywracaniu danych w złożonych formularzach lub kreatorach wieloetapowych.
- Utrata kontekstu: Nieprzywracanie użytkownika do tej samej strony lub widoku, na którym był przed wygaśnięciem sesji (np. zamiast tego przekierowywanie na stronę główną).
- Utrata stanu interfejsu: Nieprzywracanie filtrów, sortowania, stanu rozwinięcia/zwinięcia elementów, co zmusza użytkownika do ponownej konfiguracji widoku.
- Niewystarczające testy: Brak kompleksowych testów wszystkich możliwych scenariuszy wygaśnięcia sesji.
- Niebezpieczne przechowywanie danych: Przechowywanie wrażliwych danych użytkownika w
localStorage
lubsessionStorage
, co stanowi poważne zagrożenie bezpieczeństwa. - Mylenie „wylogowania” z „wygaśnięciem sesji”: Kryterium dotyczy sytuacji, gdy sesja wygasa bez celowego wylogowania przez użytkownika. W przypadku świadomego wylogowania, utrata danych jest zazwyczaj akceptowalna, o ile użytkownik został o tym poinformowany lub miał możliwość zapisania pracy.