Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

oauth2: Consider implementing OIDC Session Management #834

Closed
aeneasr opened this issue May 1, 2018 · 20 comments
Closed

oauth2: Consider implementing OIDC Session Management #834

aeneasr opened this issue May 1, 2018 · 20 comments
Assignees
Labels
feat New feature or request.
Milestone

Comments

@aeneasr
Copy link
Member

aeneasr commented May 1, 2018

This has come up once or twice now, where developers want users to be able to log out of all applications. OIDC has a spec for that (or rather 3 different drafts) and the flows are summarized here.

Personally, I'm absolutely not a fan of invisible iframes, weird inter-iframe javascript communication, and in general those specs. Let's see how they evolve and consider implementing them once they leave draft status and are an official spec.

@aeneasr aeneasr added the feat New feature or request. label May 20, 2018
@aeneasr aeneasr added this to the unplanned milestone May 20, 2018
@smyrgl
Copy link

smyrgl commented Jul 3, 2018

This would be a very nasty way to go on iOS because the security model is moving towards using SFAuthenticationSession (iOS11) and ASWebAuthenticationSession (iOS12+) which create out-of-process (sandboxed) Safari instances that the app has no control over. Thus there is no way to invisibly call an iframe and the cookies themselves that are used for tracking the session are not directly accessible. The only feasible means to do session management assuming this model is adopted is having a way to manually revoke the token which would also kill the session remember token.

@aeneasr
Copy link
Member Author

aeneasr commented Jul 4, 2018

Good point, this sort of confirms my negative bias towards the spec. We'll definitely figure something out. I have no iOS experience however.

Just to confirm, what you're saying is that a REST call with the token which revokes the session completely is the way to go here?

@smyrgl
Copy link

smyrgl commented Jul 5, 2018

That would be my preference and I don’t see any other way that would really work. They aren’t mutually exclusive though so have a revoke endpoint that purges the session could be done for use cases like admin or mobile.

It might be good though to keep these separate. An admin endpoint would want to arbitrarily revoke sessions (token or users) but a POST to a /logout for a given session could simply use the Authorization header and return a 204 thus only expiring the session token associated with the given user.

@aeneasr
Copy link
Member Author

aeneasr commented Aug 21, 2018

We have now all the endpoints to deal with basic logout flows. I still think this spec is outdated and very complicated. The OIDC foundation doesn't certify this spec either. Closing.

@aeneasr aeneasr closed this as completed Aug 21, 2018
@aeneasr
Copy link
Member Author

aeneasr commented Nov 3, 2018

I'm reopening this because the SSO requirements come up over and over again, and hydra lacks a peer reviewed flow for this. It makes sense to implement at least some of these flows.

@DASPRiD
Copy link
Contributor

DASPRiD commented Apr 14, 2019

I see a big problem with the currently implemented way for revoking sessions. First of (because I saw false assumptions spread over other sides): revoking the session on hydra is obviously required for SPA applications, since even if they'd forget the access token, the next silent refresh would automatically get new tokens (as long as the user has chosen to remember the login).

Anyway, the problem I'm seeing with /oauth2/auth/sessions/login/revoke is (correct me if I'm wrong), that it's just a GET URL without any proof of origin. Thus someone could easily make a CSRF attack against it by publishing an <img/> tag pointing to it somewhere, which would log out any user visiting the containing page.

@aeneasr
Copy link
Member Author

aeneasr commented Apr 15, 2019

I see a big problem with the currently implemented way for revoking sessions. First of (because I saw false assumptions spread over other sides): revoking the session on hydra is obviously required for SPA applications, since even if they'd forget the access token, the next silent refresh would automatically get new tokens (as long as the user has chosen to remember the login). revoking the session on hydra is obviously required for SPA applications, since even if they'd forget the access token, the next silent refresh would automatically get new tokens (as long as the user has chosen to remember the login).

An access token is not proof of authentication. If you want to remove a session along with any access/refresh tokens just delete the cookie or localStorage item.

Also, if you use silent refresh, you do not have a refresh token, that's the whole point of the flow.

Anyway, the problem I'm seeing with /oauth2/auth/sessions/login/revoke is (correct me if I'm wrong), that it's just a GET URL without any proof of origin. Thus someone could easily make a CSRF attack against it by publishing an tag pointing to it somewhere, which would log out any user visiting the containing page.

Yes, the current logout flow is vulnerable to DoS, but the impact is not very high as only the login session in ORY Hydra is removed.

@aeneasr aeneasr modified the milestones: v1.1.0, v1.0.0 Apr 15, 2019
@aeneasr
Copy link
Member Author

aeneasr commented Apr 15, 2019

Architecture/implementation notes:

Specs front/backchannel log out seem much more important than OpenID Connect Session Management:

For logout, it makes sense to add another UI interaction:

  • GET /oauth2/auth/session/logout (public) initiates a log out session and redirects to ${urls.logout}?challenge=.... Supported parameters:
    • 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 is used as an indication of the identity of the End-User that the RP is requesting be logged out by the OP. The OP need not be listed as an audience of the ID Token when it is used as an id_token_hint value.
    • 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, either using the post_logout_redirect_uris Registration parameter or via another mechanism. If supplied, the OP SHOULD honor this request following the logout.
    • 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. If included in the logout request, the OP passes this value back to the RP using the state query parameter when redirecting the User Agent back to the RP.
  • GET /oauth2/auth/requests/logout?challenge=${challenge} returns information on the logout request. This may contain data in case of OIDC front-channel logout.
    �- PUT /oauth2/auth/requests/logout/accept?challenge=${challenge} (admin) completes the logout.
    �- PUT /oauth2/auth/requests/logout/reject?challenge=${challenge} (admin) rejects the logout.

OIDC Front Channel Implementation summary

Clients that support OIDC Front Channel logout register the following values during OIDC DCR:

  • frontchannel_logout_uri: RP URL that will cause the RP to log itself out when rendered in an iframe by the OP. An iss (issuer) query parameter and a sid (session ID) query parameter MAY be included by the OP to enable the RP to validate the request and to determine which of the potentially multiple sessions is to be logged out; if either is included, both MUST be.
  • frontchannel_logout_session_required: Boolean value specifying whether the RP requires that iss (issuer) and sid (session ID) query parameters be included to identify the RP session with the OP when the frontchannel_logout_uri is used. If omitted, the default value is false.
  • post_logout_redirect_uris: Array of URLs supplied by the RP to which it MAY request that the End-User's User Agent be redirected using the post_logout_redirect_uri parameter after a logout has been performed.

The log out page then should render a list of iframes:

  • <iframe src="frontchannel_logout_uri"> and append ?iss=http://issuer-url/&sid=${session-id-from-id-token}

The values for that can be retrieved from the GET /oauth2/auth/requests/logout?challenge=${challenge} response.

Hydra should advertise at OIDC Discovery:

  • frontchannel_logout_supported: Boolean value specifying whether the OP supports HTTP-based logout, with true indicating support. If omitted, the default value is false.
  • frontchannel_logout_session_supported: Boolean value specifying whether the OP can pass iss (issuer) and sid (session ID) query parameters to identify the RP session with the OP when the frontchannel_logout_uri is used. If supported, the sid Claim is also included in ID Tokens issued by the OP. If omitted, the default value is false.
  • end_session_endpoint: URL at the OP to which an RP can perform a redirect to request that the End-User be logged out at the OP (/oauth2/auth/session/logout)

The ID Token must contain a sid claim:

  • sid - String identifier for a Session. This 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.

Example iframe request:

https://rp.example.org/frontchannel_logout
    ?iss=https://server.example.com
    &sid=08a5019c-17e1-4977-8f42-65a12843ea02

OPs supporting HTTP-based logout need to keep track of the set of logged-in RPs so that they know what RPs to contact at their logout URIs to cause them to log out. Some OPs track this state using a "visited sites" cookie. OPs contact them in parallel using a dynamically constructed page with HTML <iframe src="frontchannel_logout_uri"> tags rendering each logged-in RP's logout URI.

Back-channel logout

Discovery:

  • backchannel_logout_supported: Boolean value specifying whether the OP supports back-channel logout, with true indicating support. If omitted, the default value is false.
  • backchannel_logout_session_supported: Boolean value specifying whether the OP can pass a sid (session ID) Claim in the Logout Token to identify the RP session with the OP. If supported, the sid Claim is also included in ID Tokens issued by the OP. If omitted, the default value is false.

Client meta:

  • backchannel_logout_uri: RP URL that will cause the RP to log itself out when sent a Logout Token by the OP.
  • backchannel_logout_session_required: Boolean value specifying whether the RP requires that a sid (session ID) Claim be included in the Logout Token to identify the RP session with the OP when the backchannel_logout_uri is used. If omitted, the default value is false.

The first value, if set, indicates that backchannel log out should run if we log out a user.

When PUT /oauth2/auth/requests/logout/accept?challenge=${challenge} is called, Hydra sends a POST request to backchannel_logout_uri :

  POST /backchannel_logout HTTP/1.1
  Host: rp.example.org
  Content-Type: application/x-www-form-urlencoded

  logout_token=eyJhbGci ... .eyJpc3Mi ... .T3BlbklE ...

The ID Token has the following claims. BE AWARE THAT THE nonce CLAIM SHOULD NOT BE SET.

iss REQUIRED. Issuer Identifier, as specified in Section 2 of [OpenID.Core].
sub OPTIONAL. Subject Identifier, as specified in Section 2 of [OpenID.Core].
aud REQUIRED. Audience(s), as specified in Section 2 of [OpenID.Core].
iat REQUIRED. Issued at time, as specified in Section 2 of [OpenID.Core].
jti REQUIRED. Unique identifier for the token, as specified in Section 9 of [OpenID.Core].
events REQUIRED. Claim whose value is a JSON object containing the member name http://schemas.openid.net/event/backchannel-logout. This declares that the JWT is a Logout Token. The corresponding member value MUST be a JSON object and SHOULD be the empty JSON object {}.
sid OPTIONAL. Session ID - String identifier for a Session. This 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.

A Logout Token MUST contain either a sub or a sid Claim, and MAY contain both. If a sid Claim is not present, the intent is that all sessions at the RP for the End-User identified by the iss and sub Claims be logged out.

  {
   "iss": "https://server.example.com",
   "sub": "248289761001",
   "aud": "s6BhdRkqt3",
   "iat": 1471566154,
   "jti": "bWJq",
   "sid": "08a5019c-17e1-4977-8f42-65a12843ea02",
   "events": {
     "http://schemas.openid.net/event/backchannel-logout": {}
     }
  }

OPs supporting back-channel logout need to keep track of the set of logged-in RPs so that they know what RPs to contact at their back-channel logout URIs to cause them to log out. Some OPs track this state using a "visited sites" cookie. OPs are encouraged to send logout requests to them in parallel.

@DASPRiD
Copy link
Contributor

DASPRiD commented Apr 15, 2019

An access token is not proof of authentication. If you want to remove a session along with any access/refresh tokens just delete the cookie or localStorage item.

Also, if you use silent refresh, you do not have a refresh token, that's the whole point of the flow.

Well, that's not completely true. When you use the authorization code flow, which is the currently recommended flow for SPAs, you do in fact get a refresh token. But as the local storage is not considered a secure storage, both the access token and the refresh token are only kept in memory, so that the access token can be used against the resource API and the refresh token can be used to refresh the access token while the tab is open.

Only as soon as the page is reloaded, silent renew is required to obtain a new set of tokens. So at this point it is required to remove the hydra session in case the user wants to truly log out.

Yes, the current logout flow is vulnerable to DoS, but the impact is not very high as only the login session in ORY Hydra is removed.

This might not be important for non-SPAs, but SPAs themself are more vulnerable to this. Do I see it correctly from your second post that the Front Channel logout would solve this problem?

@aeneasr
Copy link
Member Author

aeneasr commented Apr 15, 2019

Only as soon as the page is reloaded, silent renew is required to obtain a new set of tokens. So at this point it is required to remove the hydra session in case the user wants to truly log out.

Cycling back since you've updated your initial comment:

I see a big problem with the currently implemented way for revoking sessions. First of (because I saw false assumptions spread over other sides): revoking the session on hydra is obviously required for SPA applications, since even if they'd forget the access token, the next silent refresh would automatically get new tokens (as long as the user has chosen to remember the login).

This is not true, removing the login state at Hydra implies that silent refresh will fail, thus new authorization is required.

This might not be important for non-SPAs, but SPAs themself are more vulnerable to this. Do I see it correctly from your second post that the Front Channel logout would solve this problem?

That's actually the trade-off the official OIDC spec makes wrt session management. Passing an id_token_hint solves that problem (it's basically the equivalent to a CSRF token) but it's not required:

8.  Security Considerations

  The OP iframe MUST enforce that the caller has the same origin as its parent frame. It MUST reject postMessage requests from any other source origin, to prevent cross-site scripting attacks.

  The id_token_hint parameter to a logout request can be used to determine which RP initiated the logout request. Logout requests without a valid id_token_hint value are a potential means of denial of service; therefore, OPs may want to require explicit user confirmation before acting upon them.

The current flow mimics the least common (security) denominator. It's not great, but it's also not a major security flaw.

Do I see it correctly from your second post that the Front Channel logout would solve this problem?

As long as an id_token_hint is passed, yes.

In general there should be a user interaction before logging someone out of the OP. We'll probably not do that for 1.0 as it's too much work but instead enforce id_token_hint.

@DASPRiD
Copy link
Contributor

DASPRiD commented Apr 15, 2019

This is not true, removing the login state at Hydra implies that silent refresh will fail, thus new authorization is required.

Well, that is exactly what I want, when the user clicks "logout" in the application :)

@aeneasr
Copy link
Member Author

aeneasr commented Apr 15, 2019

Then your use-case is already supported!

@DASPRiD
Copy link
Contributor

DASPRiD commented Apr 15, 2019

You mean, by redirecting the user to /oauth2/auth/sessions/login/revoke? Well, kinda. The problem with that one (at the moment) is that after that, the user gets redirected to a single (to hydra) hardcoded URL, instead of the application being able to get redirected back to.

@aeneasr
Copy link
Member Author

aeneasr commented Apr 15, 2019

You mean, by redirecting the user to /oauth2/auth/sessions/login/revoke? Well, kinda. The problem with that one (at the moment) is that after that, the user gets redirected to a single (to hydra) hardcoded URL, instead of the application being able to get redirected back to.

Not true, the URL is not hardcoded. You can set it via configuration. What's currently not possible is whitelisting redirect URLs on a pre-client basis. Leaving the URL open is a security thread (open redirect) which is why it has to be whitelisted.

@DASPRiD
Copy link
Contributor

DASPRiD commented Apr 15, 2019

Well, by hardcoded I meant what is defined globally for Hydra. And yeah, obviously it needs to be whitelisted on a per-client basis.

@jeremiahsmall
Copy link

Yes, redirection to a configured-per-app logged out url is needed. Else this tightly binds all the authorized apps on a hydra server to one logout flow.

@jacksontj
Copy link

I have run into this question/problem myself. What I'm seeing now is that I have 1 API to revoke sessions from my IDP (RevokeUserClientConsentSessions). I actually keep track of IDPsessions <-> oauthChallenge, so when an IDP session is expired/revoked/etc. I actually know which UserClientContentSession to revoke, but right now the only admin API endpoint revokes all -- instead of just the subset I know I should invalidate. From my perspective adding an admin API which would allow me to delete/expire specific tokens looks like a simple change that enables a lot of these solutions. All of these session management specs are effectively different ways to tell the OIDC service which token to revoke, so if an admin API was added to revoke single tokens -- this would allow hydra to not actually implement any of those specs but still enable others (like myself) to implement around hydra to build our custom logic outside and just API interract with hydra.

@Renkas
Copy link

Renkas commented Mar 27, 2020

I'm not really sure why this issue has been closed. Yes you implemented front-/backchannel logout but not OIDC Session Management.

Maybe I'm missing something obvious - but I can't figure out how I can implement same functionality with just frontchannel logout.

Let me clarify my needs:

  • I have multiple software platforms (main site, forum, some smaller minisites) that use Hydra as SSO provider.
  • I have a different OIDC client for every piece of software and I have successfully implemented SSO flows so if people log in (for example) through forum then they will be silently also logged in to other sites aswell when they visit them (OIDC silent login - prompt=none).
  • Now I have a need to also log user out of all these different pieces of software when user logs out in one of them.

As far as i understand OIDC Session Management would help me with that. Or maybe I'm misionformed and there is some other way to achieve this? I would be thankful for a nudge to the right path then.

@aeneasr
Copy link
Member Author

aeneasr commented Mar 28, 2020

I think this was by accident due to GitHub Projects!

@aeneasr aeneasr reopened this Mar 28, 2020
@aeneasr
Copy link
Member Author

aeneasr commented Mar 29, 2020

I think this was by accident due to GitHub Projects!

Actually no, this was closed by a commit that introduced OIDC Front/Backchannel logout. I don't think that there is a lot of additional benefit to OIDC Session Management. Closing again.

Now I have a need to also log user out of all these different pieces of software when user logs out in one of them.

As far as i understand OIDC Session Management would help me with that. Or maybe I'm misionformed and there is some other way to achieve this? I would be thankful for a nudge to the right path then.

OIDC SM can help with logout but so do OIDC Front/Backchannel Logout that are already implemented!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat New feature or request.
Projects
None yet
Development

No branches or pull requests

6 participants