Sign-out flows are UI and state hygiene
A user who signs out is asking for a complete exit from a personalized experience. The sign-out flow therefore has two equally important jobs: it must communicate clearly and securely that the user is no longer signed in, and it must actually make that condition true across every part of the browser that may have stored sensitive state. This guide breaks down the UX and technical requirements for doing both reliably.
UX requirements for sign-out
A good sign-out button or link is visible, consistently placed, and unambiguous. Avoid obscure menus or labels that leave users guessing. Since accidental sign-outs are a real risk, present a confirmation prompt before finalizing the action. This gives users a chance to reconsider, including those who intentionally rely on their device's own lock screen and authentication rather than signing out of every site.
After sign-out, redirect to a secure landing page that clearly shows the signed-out state. Make sure the redirect target contains no personalized content and is not an open redirect that an attacker could abuse. If the user is signed in through a federated identity provider, check whether the provider supports a logout you should also trigger. Also be aware that if the provider allows silent auto sign-in, you may need to suppress it after sign-out.
The sign-out control itself must be usable with assistive technologies and keyboard navigation, and the flow should be tested across browsers and devices. Error handling matters as well: if sign-out fails, give the user a clear message explaining what went wrong and what the security implications could be.
Cleaning up device-side state
The essential rule is to remove any state the server invalidated at sign-out, and to also remove sensitive session data that the server cannot reach. This covers cookies, sessionStorage, localStorage, IndexedDB, CacheStorage, and other local stores.
Do not delete everything
Focus cleanup on sensitive data. Wiping the entire local store makes the experience worse for a user who returns later: they would face cookie consent prompts again and lose all non-sensitive preferences as if they were a first-time visitor.
Cookies on the sign-out response
In the response for the page that confirms sign-out, send Set-Cookie headers to clear every sensitive cookie. Set the expires attribute to a past date and empty the value.
Set-Cookie: sensitivecookie1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; secure
Set-Cookie: sensitivecookie2=; expires=Thu, 01 Jan 1970 00:00:00 GMT; secure
...
When the user is offline
The server-response approach fails if the user attempts sign-out while offline. One pattern is to use two cookies for the signed-in state: an HTTPS-only secure cookie and a separate cookie readable via JavaScript. When offline, your code can clear the JavaScript-accessible cookie and complete whatever other local cleanup is possible. With a service worker in place, the Background Fetch API lets you retry the server-side state clearing once connectivity returns.
Local and session storage
Session storage normally dies when the tab session ends, but a user may sign out while other tabs remain open. Clear sensitive entries from sessionStorage proactively at sign-out.
// Remove sensitive data from sessionStorage
sessionStorage.removeItem('sensitiveSessionData1');
// ...
// Or if everything in sessionStorage is sensitive, clear it all
sessionStorage.clear();
localStorage, IndexedDB, and Cache APIs persist across sessions completely, so any sensitive data placed there must be cleared at sign-out.
// Remove sensitive data from localStorage:
localStorage.removeItem('sensitiveData1');
// ...
// Or if everything in localStorage is sensitive, clear it all:
localStorage.clear();
// Delete sensitive object stores in indexedDB:
const name = 'exampleDB';
const version = 1;
const request = indexedDB.open(name, version);
request.onsuccess = (event) => {
const db = request.result;
db.deleteObjectStore('sensitiveStore1');
db.deleteObjectStore('sensitiveStore2');
// ...
db.close();
}
// Delete sensitive resources stored with the Cache API:
caches.open('cacheV1').then((cache) => {
await cache.delete("/personal/profile.png");
// ...
}
// Or better yet, clear a cache bucket that contains sensitive resources:
caches.delete('personalizedV1');
Caches the browser manages itself
The HTTP cache never has to be manually cleared if you return sensitive resources with a Cache-control: no-store header. The back/forward cache will likewise discard pages it should not hold onto, provided those responses had that header. The back/forward cache also evicts same-origin pages when it observes certain events: deletion or modification of secure HTTPS-only cookies, or a response from a page-initiated XHR or fetch that carried Cache-control: no-store.
Keeping other tabs consistent
Users rarely close every tab or window before signing out. Expecting otherwise is fragile. Design so that all open views of your site notice the revoked login without user intervention. A useful pattern combines lifecycle events with cross-tab messaging.
The pageshow hook
The pageshow event fires before a page restored from back/forward navigation paints its first frame. Use that moment to re-test signed-in state and clear the page's sensitive content or the whole page if login is gone.
window.addEventListener('pageshow', (event) => {
if (event.persisted && !document.cookie.match(/my-cookie)) {
// The user has logged out.
// Force a reload, or otherwise clear sensitive information right away.
body.innerHTML = '';
location.reload();
}
});
Broadcast Channel for live updates
The Broadcast Channel API is well suited to telling other tabs about an auth change. When a user signs out, send the event across the channel so every sensitive tab either clears its data or navigates to the signed-out landing page.
// Upon logout, broadcast new login state so that other tabs can clean up too:
const bc = new BroadcastChannel('login-state');
bc.postMessage('logged out');
// [...]
const bc = new BroadcastChannel('login-state');
bc.onMessage = (msgevt) => {
if (msgevt.data === 'logged out') {
// Clean up, reload or navigate to the sign-out page.
// ...
}
}
State consistency as the goal
The sign-out flow succeeds only when both the visible page and all locally held state agree with the user's intent. The correct redirect target, deleted cookies and storage entries, correct cache headers, and cross-tab synchronization all work toward a single result: a user who sees no trace of their previous session and is confident no one else can either.



