Client Certificates and Logout

Back in May, I wrote about Client Certificate Authentication, a mechanism that allows websites to strongly validate the identity of their visitors using certificates presented by the visitor’s browser.

One significant limitation for client certificate authentication is that there is no standards-based mechanism for a user to “log out” of a site that uses that auth mechanism.

Background on Auth Caching

When a user picks a client certificate to authenticate to a server, that choice (an {Origin, ClientCert} tuple) is remembered for the lifetime of the browsing session. Any subsequent CertificateRequest challenge from that origin will automatically be answered by the browser using the previously-selected certificate.

The Auth Caching behavior is necessary for at least two reasons:

  • User-Experience: Client Cert authentication is a connection-oriented authentication protocol– each connection to the server can challenge the user for a client certificate, and the browser re-prompting the user for a new certificate for each new connection would be incredibly annoying.
  • Predictability: Even worse, if the user accidentally picked a different certificate for subsequent connections, the browser would end up in the very confusing state whereby requests might exhibit different behavior based on which reused connection happened to be pulled from the browser’s connection reuse pool when each request is sent.

Unfortunately, tying the Auth cache’s certificate selection to the browsing session means that if a user has multiple certificates, selecting a different certificate for subsequent use on the site (either because the user chose the “wrong” certificate the first time, or because they would like to “switch their identity”) is not generally straightforward.

The only option that works reliably across browsers is to restart the browser entirely (closing all of your other tabs and web applications).

Log Out Approaches

To address the user desire to “Log out without restarting,” Internet Explorer offered a “Clear SSL State” button buried inside the Internet Control Panel:

… and websites could invoke the same functionality with a web-accessible JavaScript call:

 document.execCommand("ClearAuthenticationCache", false);

Notably, the button and the API both clear all Client Certificates for all sites running in the current session, meaning users cannot log out of just a single site using the API.

The ClearAuthenticationCache API (a blunt hammer with many side-effects) was never standardized, so it was not supported in other browsers.

Instead, the standardized Clear Site Data (MDN) offers a mechanism for websites to programmatically clear per-origin data including Auth Caches (both HTTP Authentication and Client Certificate Authentication). Unlike the IE ClearAuthenticationCache call, Clear-Site-Data is per-origin, so calling it does not blow every site’s data away.

Unfortunately, it’s not very useful for logging users out of Client Cert auth’d sites, for a few reasons:

  1. Websites cannot clear only Auth caches– the Auth caches are instead cleared when the website indicates that it would like cookies cleared.
  2. Websites cannot clear only Session data– when you request that cookies be cleared, it clears not only the Auth caches and session cookies, but also clears the origin’s persistent cookies.
  3. Clearing the Auth Cache alone isn’t enough– the browser also must clear/close any authenticated connections to the origin from the reuse pool, because otherwise a request might reuse a connection previously authenticated with a client certificate.

    The complexity and side-effects of this requirement mean that Chromium has not implemented the clearing of the Auth cache when Clear-Site-Data is called.

Chromium also does not offer any UI mechanism to clear auth caches, meaning that users must restart their browser to clear the auth cache and select different credentials.

Sessions vs. Profiles

Beyond the Clear SSL State button and ClearAuthenticationCache API, Internet Explorer offered users a New Session command that allows a user to open a new browser window that runs in a different Session than the original — the new browser window’s Session does not share Session state (the auth cache, session cookies, sessionStorage) with the original Session. The new Session does however share persistent state (persistent cookies, localStorage, the HTTP Cache, etc), so the two Sessions are not fully isolated from one another.

In contrast, Chromium does not offer such a “New Session” feature. Users can have a maximum of two active Sessions per profile– their “main” Session, and one Private Mode (“Incognito”) session. If a user wishes to have more than two Sessions active at a time, they must load the site using multiple Browser Profiles.

By many measures, this design is “better”– using a different Profile means that all state is isolated between the instances, so you won’t have localStorage, indexedDB data, persistent cookies, or HTTP cache information cross-contaminating your different browser sessions. You’ll also have a handy Profile Avatar in your browser UI to remind you of which account you’re supposed to be using for each window.

However, to an end-user, using different profiles might be less convenient, because profiles isolate all state, not just web platform state. That means that Favorites, Extensions, History and other useful application state are not shared between the two Profiles.

Can anything be done?

There are a variety of investments we might consider to address this use case:

  1. Enhance Chromium’s Clear Site Data feature to match the spec. (The fact that this got punted in Chromium suggests that it’s a hard problem)
  2. Offer the user an explicit mechanism in the browser UI to clear an origin’s Auth cache and empty its connection-reuse pool. (Users will have to learn the new mechanic)
  3. Try to be clever and clear the Auth cache when the user closes the last top-level browser tab to an origin. This is not quite as crazy as it sounds; there have been some discussions of this approach to address the problem of undead session cookies. (Tricky to implement this without undesirable side effects)
  4. Implement something like Firefox Containers to allow one Browser Profile to contain multiple isolated Web Platform-level sessions/profiles. (Expensive, Complicated, Users will have to learn the new mechanic)
  5. The clever and simple solution for this longstanding problem that I simply haven’t thought of yet.

Given the relative obscurity of this scenario, I’m hoping #5 turns up. :)

-Eric

Bonus Content: PINs

One additional behavioral change between IE/Edge Legacy and the new Edge concerns SmartCards whose private keys are protected by a PIN.

Windows typically caches a user-entered PIN on a per-process basis.

In the old model, each tab’s content process contained its own instance of the network stack which was responsible for selection of the certificate (and obtaining the user’s PIN, if required). This means each individual tab would prompt the user for their SmartCard PIN.

In the new model, it’s the browser process that accesses the SmartCard to obtain a selected client certificate, which means that the user is prompted for their PIN only once per browsing session.

Published by ericlaw

Impatient optimist. Dad. Author/speaker. Created Fiddler & SlickRun. PM @ MSFT '01-'12, and '18-, presently working on Microsoft Edge. My words are my own.

One thought on “Client Certificates and Logout

  1. A thought I had was a sessionStorage.setItemLife(‘mysessionid’, 1800);
    Where 1800 is the amount of seconds from when setItemLife is called on the item (i.e. from ‘now’).
    Alternatively a third argument could be added like this sessionStorage.setItem(‘mysessionid’, ‘a12b34c56d78e90f’, 1800);

    One could argue that if you are already using javascript one could just set a timer that triggers a function to delete the item. But scripts or pages can be paused (put to sleep?). This would make the browser clear the item regardless of page states.
    And obviously the sessionid should time out server side as well.
    But what if this was not just a sessionid, what if it was somebody’s name or even a (unhashed?) password or something else that is sensitive.

    Cookies do have a expiration that can be set, so with my suggestion there would be a parity between cookies and sessions in that respect. Even localStorage might benefit from this (long term storage that isn’t permanent/unspecified, for example 2592000 seconds = 30 days.)

    Myself I use sessionStorage for well… session/temporary stuff (instead of cookies), and localStorage for long term (color preferences or similar user settings that should stick), deleting these localStorage items would require the user interacting with the origin site. With a optional timeout/lifespan this could be done during a browser’s maintenance routines.

    For localStorage such a life number could be used as a strong hint for the garbage/trash/cleanup collector routines. For sessionStorage it would be a privacy indicator where the browser will delete it at first opportunity (within a second I’ll assume).

    However this still won’t solve the issue of HTTP Auth though. My suggestion for that would be to amend the HTTP Auth specs to specify a timeout.

    Perhaps with WWW-Authenticate: Basic realm=”Access to the staging site”, charset=”UTF-8″
    there could be added a WWW-Authenticate: Basic realm=”Access to the staging site”, charset=”UTF-8″, timeout=”1800,unload”

    Once 1800 seconds has passed then the auth session/credentials are deleted and the browser will unload the page. If the unload keyword is not there (just the seconds) then only the credentials are deleted.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s