OpenID Connect Logout
The OpenID Connect (OIDC) family of specs supports logout (from a single application) and global (or single) logout (from all applications that the user has logged into through the OpenID Provider, OP), but these features are optional or in draft status (as of Q2, 2017). So, these spec features may not be available in all OIDC implementations (yet); though, most enterprise-grade OP implementations would include these features or something similar. This blog post explores the use of OIDC for logout of the local application and global logout of all applications that have a security session (for a particular user) with an OP. The OIDC family of specs contains three specs relevant to session termination (logout):
- OpenID Connect Session Management v1.0 Spec
- OpenID Connect Front-Channel Logout v1.0 Spec
- OpenID Connect Back-Channel Logout v1.0 Spec
The Session Management Spec describes how the Relying Party (RP) obtains security session state from the OP and communicates a logout request to the OP. From the spec, “This specification complements the OpenID Connect Core 1.0 [OpenID.Core] specification by defining how to monitor the End-User’s login status at the OpenID Provider on an ongoing basis so that the RP can logout an End-User who has logged out of the OpenID Provider.” It further stipulates that the user can be prompted for whether they want to be globally logged out. If so, then there are three options for communicating the logout request to other applications the user is logged into on the OP: the Session Management mechanism (active polling by RPs within the browser), front-channel communication and back-channel communication (both of which rely upon the OP pushing the logout request to the RPs).
The front-channel communication is performed through a user-agent. This is analogous to how SAML2 Single Logout works. An important difference is that to make the process less fragile (by taking less time) a page is constructed with an iframe referencing the logout URI for each RP that has a (security) session with the OP — this way, the logout request to each RP occurs in parallel (rather than a serialized series of redirects to perform logout on each RP).
For the back-channel communication, the logout is performed directly between the OP and RPs via the OP making a call to a registered logout URL. As such, there must be connectivity between the OP and RP logout URLs. These calls are also supposed to happen in parallel.
If there is a backend that the application interacts with that has a security session for the user (this can come in several forms), it may be that the logout process simply clears the locally cached tokens in the User-Agent and refreshes the UI to reflect session termination. Sometimes, something like an API Gateway may serve as the server-side component that advertises the logout URI, but that component must be controlled by the same entity that controls the RP and the application security model must meet certain assumptions that lend itself to such a design (a concept of security session state in the form of an authentication cache, for example). If there is no API Gateway, then it would fall on the API Provider to advertise the logout URL — the same commentary applies.
While OIDC does define global logout functionality, it isn’t commonly used in corporate IT shops yet (probably because OIDC is a relatively new protocol and the session management related specs are still in draft status). Typically, there is logout from the local application (and, probably, the IdP), but not logout from all applications. As OIDC adoption grows, this functionality will almost certainly become more common. Some organizations may implement a unique logout application that is an RP itself that can trigger the logout of all or a subset of a user’s active session for an OP. This avoids each application having to implement such functionality.
Why are there three different ways of achieving the same thing? Flexibility? Side effect of the committee process? It will be interesting to see what the final RFCs for OIDC session management and logout look like. As noted in the When To Use Which (OAuth2) Grants and (OIDC) Flows post, ideally, your OP vendor has made these design decisions for you as part of their architecture.
Before we go further, a few words of warning:
- Any logout mechanism that is dependent upon user agent (web browser) HTTP redirects to handle communicating the logout request between the OP and RP can be fragile. Browsers can crash, hang, close before the logout sequence is complete (user closes the tab), etc, etc.
- The HTTP protocol is a stateless, unreliable protocol that occasionally requires the end user to resubmit requests. This is not a good protocol to build a guaranteed logout mechanism upon. The required level of assurance for logout of your online banking application is much greater than for Facebook. Though, it would be nice to have an equal assurance for both.
- To minimize potential problems created by these issues, a reverse proxy technology such as TAMeb/ISAM, Ping Access, or similar could be used. This doesn’t work in all application architectures and is not popular in API-based communication.
- Since all three of these specs are not yet finalized, important details may change.
To avoid complexity, it is possible, where security constraints allow, to simply rely upon a timeout based logout mechanism for the RP and OP. There are a couple of approaches.
- The OIDC ID Token has an expiration timestamp. This timestamp can represent the session timeout for the RP or the valid lifetime for which the token can be used to create a session. If it’s the prior, then any request that is received after this timestamp should be considered invalid and the user’s session is expired. If refresh tokens are being used, this doesn’t really work. If information security policy requires short-lived tokens that should only be valid long enough to create the applications security session, then this doesn’t really work, which is often the case.
- The application can also establish its own session timeout that it enforces. A common timeout value can be established with the OP that essentially achieves the desired result. In situations like this, configuring the OP to have user’s session expire a few moments before the RP is advisable. It’s also possible that the desired behavior is to allow the user to maintain long security sessions on the RP, but to have to periodically reauthenticate against the identity provider. The desired behavior and requirements can differ significantly across applications.
Single Application (RP) Logout
Let’s start with the basic use case — logout from a single application.
There is a user logged into an application (an RP) that authenticated against an OP. The user needs to logout of the RP and OP in such a manner as to ensure that if the user goes back to the RP URL, then the user will be prompted for credentials again.
The Session Management spec describes how to do this. From the spec, “This specification complements the OpenID Connect Core 1.0 [OpenID.Core] specification by defining how to monitor the End-User’s login status at the OpenID Provider on an ongoing basis so that the Relying Party can logout an End-User who has logged out of the OpenID Provider.”
OP Initiated Logout
The Session Management spec spends most of its efforts defining an OP initiated logout mechanism; though, it never uses this phrase when describing it. I rather like the “OP initiated logout” moniker because it is descriptive and in line with the IdP initiated logout that was encountered in the SAML2 use cases.
The OIDC session starts when the RP validates the End User’s ID Token that is included in the response from OP token endpoint. For more information on OP session creation, see my OIDC Series. The Session Management spec requires that an additional parameter, session_state, be included in the OP’s Authentication Response from the authorization endpoint. The session_state value contains “a salted cryptographic hash of Client ID, origin URL, and OP browser state” — check Section 4.2 of the Session Management spec for sample code and further details.
If the RP wants to terminate a session when the OP session terminates (due to timeout or user logging out), then the RP must periodically check on the session status on the OP. One approach is to to repeat the Authentication Request against the OP Authorization Endpoint with the prompt=none parameter added — this is by far the simplest approach. The spec points out that this approach will generate additional network traffic that may not be desirable (think mobile devices); so, it proceeds to define a mechanism that causes no additional network traffic beyond the OP logout request by “polling a hidden OP iframe from an RP iframe with an origin restricted postMessage.”
To make this happen, the RP:
- loads an invisible iframe (from itself)
- iframe must know the ID of the OP iframe
- iframe calls Window.postMessage() on the OP iframe to determine if the IdP session is still valid.
- postMessage() call to the OP iframe should occur at regular intervals that satisfies the RP’s requirements
- postMessage() call must contain: client_id + “ “ + session_state (where client_id is the value assigned to the RP by the OP and session_state is the value received in the Authentication Response earlier)
- iframe must be able to receive a postMessage() back from the OP iframe, which will contain a “changed”, “unchanged”, or “error” message.
The RP iframe must do one of the following upon receiving the postMessage() back from the OP iframe:
- does not need to do anything in response to “unchanged”
- must perform re-authentication with prompt=none within an iframe to obtain a new ID Token and session state, sending the old ID Token as the id_token_hint when the response is “changed”. If a new ID Token is not obtained or an ID Token for a different user is obtained, the RP must handle this as a logout condition.
- must not perform re-authentication with prompt=none if the response is “error”, so as to not cause potential infinite loops (that cause a lot of network traffic). Depending on security requirements, an error may or may not need to be treated as a logout condition.
The RP performs a logout, when required (the user has requested it or a “changed” response is returned from the OP iframe call), based upon the specific mechanism it normally uses (these can be many and varied).
Pseudo-code describing what the RP iframe must do is provided in Section 4.1 of the Session Management spec.
The OP iframe has access to browser state at the OP (via cookies or HTML5 storage). The browser state is used to validate the OP session state; this approach does not generate additional network traffic on each state check. The browser state will be updated to reflect a logout at the OP or other significant events. Additional details are in section 4.2 of the Session Management spec.
Section 4.2 also has additional performance and securities concerns that must be addressed — those details are beyond the scope of this post.
RP Initiated Logout
In this case, the RP is responsible for initiation of internal security context (security session) destruction (i.e., logout the user). After that is done, the RP must redirect the user agent to the OP’s logout endpoint URL. The logout endpoint is defined by the OIDC Session Management Spec and can be obtained from the OP’s Discovery Endpoint or other proprietary mechanism. This request will look something like:
The query parameters are as follows (from the spec):
id_token_hint: Previously issued ID Token passed to the logout endpoint as a hint about the End-User’s current authenticated session with the Client. This parameter is recommended.
post_logout_redirect_uri: URL to which the RP is requesting that the End-User’s User Agent be redirected after a logout has been performed. The value must have been previously registered with the OP. This parameter is optional.
state: Opaque value used by the RP to maintain state between the logout request and the callback to the endpoint specified by the post_logout_redirect_uri query parameter. This parameter is optional.
The OIDC Session Management spec states, “at the logout endpoint, the OP should ask the End-User whether he wants to logout of the OP as well.” If yes, then in the next section the additional steps needed to ensure global logout are explored.
If the RP requested that the OP redirect the user back to the RP (via the post_logout_redirect_uri), then the OP will return a 302 redirect back to the requested endpoint (assuming the endpoint was properly registered with the OP and all other requirements are met).
Global logout is the process of terminating a user’s session on the IdP and all RPs that the user currently has an active session for. This process can be initiated by a particular RP or from the OP.
OP Initiated Logout with Front-Channel Communication
If the user
- is to be logged out of the OP
- is logged into multiple applications (RPs)
- and those applications (RPs) support OP-initiated, Front-Channel logout
then the Front-Channel Session Management specification can be used to globally log the user out of all active sessions managed by this RP.
All RPs supporting HTTP-based (front-channel) logout must register a logout URI with the OP as part of the client registration process. The RP’s logout URI must be accessible by the user’s user agent (browser).
The OP’s response to the original logout request must render
where frontchannel_logout_uri is the the registered logout URI for the additional RP that must be logged out. When the RP receives the request against its logout URI it clears the user’s security session, deletes any associated cookies, and clears HTML5 local storage (and any other activities required to terminate the user’s security session on that RP).
The following parameters may be included in the request to the logout URI:
iss: Issuer Identifier for the OP issuing the front-channel logout request.
sid: Identifier for the Session.
So, the request to the RP’s logout URI might look something like:
sid=adcd…omitted for brevity…1234=
The RP’s response should also include the appropriate Cache-Control directives to prevent the response from being cached and interfering with future logout requests.
If there is more than one additional RP, then the OP response will contain one iframe for each logged in application. This results in the logout operations for each RP occurring in parallel. From a practical standpoint, all of these logout URIs are called at the same time to provide up to the limits allowed by the browser. How many of these iframe source URIs are invoked concurrently is a function of the web browser being used. Compare this behavior to the serialized nature of logout requests to multiple applications described in the SAML2 Single Logout blog post.
If one of the RPs the user is being logged out of in this manner is also an OP that is acting as an RP for downstream security sessions, this logout request should trigger a set of logouts for those additional sessions. This can be achieved by having the downstream OP return a response containing a new iframe that triggers calls to the downstream logout URIs. This situation will be encountered when one OP is acting as an identity broker for other IdPs; the situation would be further complicated if the relationship between these IdPs is governed by multiple SSO protocols (that’s beyond the scope of this post).
RP Initiated Logout with Front-Channel Communication
The initial part of the logout process occurs in this scenario as it is described for RP-initiated logout in the Session Management spec — this is described in the “Single Application (RP) Logout” section. In the final step where the spec states that the OP should prompt the user for whether or not they wish to end their session on the OP. If OP session is terminated, then the next steps are outlined in “OP Initiated Logout with Front-Channel Communication” section.
OP Initiated Logout with Back-Channel Communication
The OIDC Back Channel Logout v1.0 spec defines an alternate mechanism for communicating logout requests to all RPs that have established sessions with an OP. This mechanism relies upon direct communication of such requests between OP and RPs — bypassing the User-Agent. In doing so, the logout mechanism eliminates a significant source of logout process fragility. However, it imposes new requirements that RPs now have a logout endpoint that is reachable by the OP. This can likely result in network paths being established (including publically accessible paths) that didn’t previously need to exist.
To begin the logout process, the OP must send a JWT called a Logout Token to the RP logout endpoint for each RP that has a session established with the OP. the Logout Token is a type of Security Event Token, which is defined by another draft spec in the OIDC family of specifications.
The following Claims are used within the Logout Token:
sub (subject identifier): This field is optional. More information can be found in the OIDC Core spec. The Logout Token must contain either a sub or sid claim — or both.
aud (audiences): This field is optional. More information can be found in the OIDC Core spec.
iat (Issued at Time): This field is optional. More information can be found in the OIDC Core spec.
jti (unique token identifier): This field is optional. More information can be found in the OIDC Core spec.
events: JSON object containing the member name:
The value must a JSON object and should be an empty JSON object. This field is required.
sid (Session Identifier): From the spec, “represents a Session of a User Agent or device for a logged-in End-User at an RP. Different sid values are used to identify distinct sessions at an OP. The sid value need only be unique in the context of a particular issuer. Its contents are opaque to the RP. Its syntax is the same as an OAuth 2.0 Client Identifier.” This field is optional.
Furthermore, the “nonce” claim must not be present in the Logout Token, the token must be digitally signed, and may be encrypted. The signing and encryption keys are the same as those used for ID Tokens.
An example of Logout Token would look something like (from the spec):
A request to the RP logout endpoint would look something like (from the spec):
POST /backchannel_logout HTTP/1.1
logout_token=eyJhbGci ... .eyJpc3Mi ... .T3BlbklE ...
Upon receiving such a request, the RP must perform the validation steps described in Section 2.6 of the OIDC Back-Channel Logout spec. If any validation step fails, the logout request is rejected and return an HTTP 400 error to the OP. Otherwise, proceed with the logout process.
The logout process on the RP is beyond the scope of these specs and is governed by the underlying platform running the application.
If the RP acts as a downstream RP, then those downstream RPs should also have logout requests sent to them to ensure all related session are terminated. This scenario is explored in a little more detail in the last section.
Refresh tokens associated with any of the terminating sessions that were issued (without the offline_access property) should be invalidated by the OP.
If the logout process was successful, then the RP returns an HTTP 200 response. A variety of error scenarios and expected responses from the RP are defined in the Back-Channel specification. The end result is that all RPs that had an active session with the OP for the end user are now terminated and the user no longer has an active session on the OP.
RP Initiated Logout with Back-Channel Communication
The description given in the “RP Initiated Logout with Front-Channel Communication” Section is relevent here except that the logout steps for all RPs other than the initiating RPs is described in the “OP Initiated Logout with Back-Channel Communication” section.
As you can see, the three Session Management-related APIs within the OIDC family of specs provides rich functionality for logout from a single application and global logout. While the OIDC approach introduces several concepts to minimize fragility and short-comings of the earlier SAML2 approach, it is still possible for the logout process to be disrupted in undesirable ways.
Lock & Key / John Fink Jr. Photography