What makes for a good sign-out experience?

Kenji Baheux
Kenji Baheux

Signing out

When a user signs out of a website, they are communicating their desire to completely get out of a personalized user experience. It is therefore important to adhere as closely as possible to the user's mental model. For example, a proper sign-out experience should also take into account any tabs that the user might have opened before they decided to sign out.

The key to a great sign-out experience can be summarized in being consistent across visual and state aspects of the user experience. This guide provides concrete advice about what to pay attention to, and how to achieve a good sign-out experience.

Key considerations

When implementing the sign-out functionality on your website, pay attention to the following aspects to ensure a smooth, secure and intuitive sign-out process:

  • Clear and consistent sign-out UX: Provide a clear and consistently visible sign out button or link that is easily identifiable and accessible throughout the website. Avoid using ambiguous labels or hiding sign-out functionality in obscure menus, subpages, or other unintuitive locations.
  • Confirmation prompt: Implement a confirmation prompt before finalizing the sign-out process. This can help prevent users from accidentally signing out, and allows users to reconsider if they really need to sign out—for example, if they diligently lock their device with a strong password or other authentication mechanism.
  • Handle multiple tabs: If a user has opened several pages from the same website in different tabs, ensure that signing out from one tab updates all other open tabs from that website as well.
  • Redirect to a secure landing page: Upon successfully signing out, redirect the user to a secure landing page that clearly indicates they are no longer signed in. Avoid redirecting users to pages that have any personalized information. Similarly, ensure that other tabs no longer reflect a signed-in state either. Also, make sure you are not building an open redirect that attackers can take advantage of.
  • Session cleanup: Once a user has signed out, completely remove any sensitive user session data, cookies, or temporary files associated with the user's session. This prevents unauthorized access to user information or account activity, and will also prevent the browser from restoring pages with sensitive information from its various caches, in particular the back/forward cache.
  • Error handling and feedback: Provide clear error messages or feedback to users if there are any issues when they sign out. Inform them of any potential security risks or data leaks if the sign-out process fails.
  • Accessibility considerations: Ensure that the sign-out mechanism is accessible to users with disabilities, including those using assistive technologies like screen readers or keyboard navigation.
  • Cross-browser compatibility: Test sign-out functionality across different browsers and devices to ensure it works consistently and reliably.
  • Continuous monitoring and updates: Regularly monitor the sign-out process for any potential vulnerabilities or security loopholes. Implement timely updates and patches to address any identified issues.
  • Identity federation: If the user is signed in using a federated identity, see if signing out from the identity provider as well is supported and needed. Also, if the identity provider supports automatic sign-in, don't forget to prevent it.

DOs

  • If you invalidate a cookie on the server as part of a sign-out flow (or other access revocation flows), make sure to delete the cookie on the user's device as well.
  • Clean up any sensitive data that you may have stored on the user's device: cookies, localStorage, sessionStorage, indexedDB, CacheStorage, and any other local data stores.
  • Ensure that any resources containing sensitive data—in particular HTML documents—are returned with the Cache-control: no-store HTTP header so that the browser doesn't store these resources in permanent storage (for example, on disk). Similarly, XHRs/fetch calls that return sensitive data should also set the Cache-Control: no-store HTTP header to prevent any caching.
  • Make sure that any opened tabs on the user's device are up to date with server-side revocations of access.

Cleaning up sensitive data upon signing out

Upon signing out, consider clearing ephemeral and locally stored sensitive data. The focus on sensitive data is motivated by the fact that clearing everything would result in a significantly worse user experience because this user may very well come back. For instance, if you were to clear all locally stored data, then your users will then have to reacknowledge cookie consent prompts, and go through other processes as though they never visited your website in the first place.

How to clean up cookies

On the response for the page which confirms the signed out status, attach Set-Cookie HTTP headers to clear every cookie that's related to, or contains, sensitive data. Set the expires value to a date in the distant past, and set the value of the cookie to an empty string for good measure.

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
...

Offline scenario

While the approach described above is sufficient for general use cases, it doesn't work if the user is working offline. You may want to consider requiring two cookies to track the signed-in state: one secure HTTPS-only cookie, and one regular cookie accessible via JavaScript. If your user is trying to sign out while offline, you could clear the JavaScript cookie and proceed with other clean-up operations if possible. If you have a service worker, you may also want to take advantage of the Background Fetch API to retry a request to clear state on the server when the user is online later.

How to clean up storage

On the response for the page which confirms the signed-out state, take care to clean up sensitive data from various data stores:

  • sessionStorage: Although this is cleared when the user terminates their session with your website, consider proactively cleaning up sensitive data when the user signs out, just in case they forget to close all the tabs opened on your website.

    // Remove sensitive data from sessionStorage
    sessionStorage.removeItem('sensitiveSessionData1');
    // ...
    
    // Or if everything in sessionStorage is sensitive, clear it all
    sessionStorage.clear();
    
  • localStorage, indexedDB, Cache/Service Worker APIs: When the user signs out, clean up any sensitive data that you may have stored using these APIs, given that such data would persist across sessions.

    // 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 via 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');
    

How to clean up caches

  • HTTP cache: As long as you set Cache-control: no-store on resources with sensitive data, the HTTP cache won't retain anything that's sensitive.
  • Back/forward cache: Similarly, if you followed the recommendations about Cache-control: no-store and about clearing sensitive cookies (for example, authentication-related secure HTTPS-only cookies) when users sign out, then you don't need to worry about sensitive data being retained in the back/forward cache. Indeed, the back/forward cache feature will evict same-origin pages served with a Cache-control: no-store HTTP header if it observes one or more of the following signals:
    • One or more secure HTTPS-only cookies have been modified or deleted.
    • One or more responses for XHRs/fetch calls—issued by the page—included the Cache-control: no-store HTTP header.

Consistent user experience across tabs

Your users may have opened many tabs for your website before they decided to sign out. By then, they might have forgotten about other tabs, or even other browser windows. It's best to avoid relying on your users to close all the relevant tabs and windows. Instead, take a proactive stance by ensuring that the user's login state is consistent across tabs.

How to

To achieve a consistent signed-in state across tabs, consider using a combination of pageshow/pagehide events and the Broadcast Channel API.

  • pageshow event: Upon a persisted pageshow, check for the user's login status and clear out sensitive data—or even the whole page—if the user is no longer signed in. Note that the pageshow event will trigger before the page is rendered for the first time upon being restored from a back/forward navigation, which guarantees that your login state check will let you reset the page to a non sensitive state.

    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();
      }
    });
    
  • The Broadcast Channel API: Use this API to communicate login state changes across tabs and windows. If the user is signed out, clear all sensitive data, or alternatively redirect to a sign-out page on all tabs and windows with sensitive data.

    // 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.
        // ...
      }
    }
    

Conclusion

By following the guidance in this document, you will be able to design a great sign-out user experience that prevents unintended logouts, and protects the user's personal information.