Local sessions in the Login endpoint to manage user profile

Posting this issue following discussion with @hackerman in the chat.

Our management wants a single IdP and wants some form of user profile page, i.e. a page where users can update their data. These users would go to login.whatever.com, sign-in and find themselves in their account page where they can do two things:

  • update the mutable profile data
  • review active consents with the ability to revoke

I understand Hydra is not an SSO solution but I still want to integrate that IdP with Hydra so that every third party client does OAuth2/OIDC.

How can we achieve this? Hydra’s own authentication session management (i.e. the remember user feature) cannot be used if you want to be able to sign-in without performing an OAuth2/OIDC flow.

I suggested the following but @hackerman came with valid counter-arguments.

Hydra docs say that we should not do any session management on the login endpoint. Is there a reason why we shouldn’t do so? Is it a security risk? Is it a spec compliance problem? It is not clear from the docs whether it’s because we don’t need to (thanks to Hydra’s session management) or whether we MUST NOT do it at all because our house will burn.
I am trying to figure out if it would be ok if we always tell Hydra to never remember auth sessions and do session management on our side instead.

@hackerman:

you MUST NOT do it, if you don’t synchornize the users you can get impersonation

@Aggouri:

I don’t really get how I can get impersonation here. I remember a user that has previously logged-in just like Hydra does with its own cookie. When performing the OAuth2 flow, hydra redirects the user to the login endpoint. Why can I not say the user is already logged-in, so let’s just get the login request from the challenge, then just tell Hydra we accept the login request and redirect back to hydra (just like Hydra does when it tells us to skip the login screen)?

@hackerman:

because hydra may remember it’s own user id
so hydra says: “yo it’s user X”, your session says: “you it’s user Y”, which user is it?

@Aggouri:

if Hydra tells me who the user is then I obviously can’t use that
that’s why I don’t want Hydra to remember sessions
I understand this risk and I guard against that specific case, i.e. in my login endpoint if the challenge tells me that Hydra tells me who the user is, I invalidate the session.
I also verified that Hydra checks for this specific case

@hackerman also highlighted the fact that foregoing Hydra’s OIDC session management would put the burden on the login endpoint to do this. He suggested some alternative solutions:

  1. using something like bitly/oauth2_proxy
  2. using Hydra’s session management and checking against the local session
  3. use a normal OAuth2 flow with the account/profile page

All come with their own set of problems/limitations :slight_smile:

I like the second approach. Let Hydra still do its OIDC session management and only use the local session if there is no incoming challenge. What bothers me is the fact that if Hydra has a valid session then you don’t allow the user to choose a different identity. That’s a UX problem or there is a burden on the third-party OAuth2 clients to play with the prompt parameter.

Are there any alternative approaches that would simplify the situation?

There are drafts regarding OIDC SSO / Session Management from the OpenID Foundation. Those are not properly implemented in ORY Hydra (yet) and are a bit of work to get done but it’s generally a possible/plausible extension.

I think this use case is definitely something we should consider implementing properly, this issue has come around repeatedly in the past few weeks. In fact, some of the log out endpoints that exist today implement parts of the OIDC Session Management drafts but lack some of their features.

So basically, the idea is that you use OIDC to log in to your profile app. The profile app may have a session based on that OIDC flow. If the profile app is done with the session, it can delete it and also tell the OP (OpenID Provider) to purge the log in session. I have to do some reading on the specs to get into more detail, but that’s more or less the idea here.

I have to do some reading on the specs to get into more detail, but that’s more or less the idea here.

@hackerman Something that goes towards the spec would be super nice, but since the spec is in draft, I understand that coming up with a proper solution on Hydra’s side will be a somewhat lengthy process (and it should be!).

It sounds like we need to find something else in the short term.

Our biggest problem right now is the inability to “Sign-in as a different user” from the login endpoint.

It would have been super-nice if there was a way for Hydra to “restart” the Login & Consent flow by letting the login endpoint tell Hydra that the user wants to sign-in as a different user. That would already nullify the user experience problem. Then, we would basically just do an OIDC flow in our profile page.

The whole complexity is in the “restart” mechanism though. It cannot and must not work for all combinations of the OIDC parameters such as “prompt”.

What you can do here is:

  1. Remove the user’s session at hydra (there’s an endpoint for that)
  2. Redirect the user to the oauth2 url (it’s in the login/consent request payload)

That should restart the flow with a fresh user!

1 Like

Thanks for the reply!

Redirect the user to the oauth2 url (it’s in the login/consent request payload)

Wow. I was so not aware about this. Thanks!

Remove the user’s session at hydra (there’s an endpoint for that)

Unless there is a REST API endpoint that I am not aware of, removing the user’s session at Hydra requires redirecting the browser to Hydra. After this, Hydra redirects the user back to the URL defined in the LOGOUT_REDIRECT_URL env var passed to Hydra at start-up.

To be able to resume the flow, we need the challenge, which is lost via the logout URL redirection.

I saw that the route below was first implemented in https://github.com/ory/hydra/issues/970 and it was a conscious choice not to allow setting the redirect URL via the browser. Through my dealings with several pentest exercises, I wholeheartedly agree with this.

GET /oauth2/auth/sessions/login/revoke:

func (h *Handler) LogoutUser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
        ...
	http.Redirect(w, r, h.LogoutRedirectURL, 302)
}

I do wonder, however, whether it would make sense to allow passing a query string to the above “revocation URL” that would be appended to the h.LogoutRedirectURL URL. In this particular case, we would be passing ?challenge=... and we would be getting ?challenge=.... This could become a usability feature if the "revocation URL` ends up having its own query string parameters, though.

  • If you see no security or usability issues with this, I could perhaps even try to take a stab in doing a PR. But as I would be doing this in my own free time, I would need a clear OK from you :slight_smile:. Also, while re-rereading my post I realise that you might prefer to first discuss this as a feature request. If so, I can instead create a ticket in GitHub.
  • The alternative I am thinking about involves storing the challenge or the “OAuth2 URL” in a local session prior to redirecting to the Hydra revocation URL and then retrieving it in the “logout redirect” endpoint to be able to restart the flow. This would unblock me right now, so it’s pretty attractive but not my preferred route.

@hackerman What do you think?

There is one: https://www.ory.sh/docs/hydra/sdk/hydra-sdk-api#invalidates-a-user-s-authentication-session

I have to think about this, it’s a trade-off for me. Assuming the query param sets e.g. the page you’re viewing (think oldschool youtube URLs with ?v=<video-id> or is vulnerable to XSS this could be a problem. On the other hand this can be very handy and we’re allowed to set certain query params in oauth2 redirects as we like. And, if an attacker is able to let you click on a log out link and then redirect you somewhere else, he/she’s also able to just send you there without a redirect. After a quick scooping of the oidc session drafts I couldn’t find guidance if this should be allowed or not. Maybe you can double-check?

However, the current (browser-based) logout is not perfect. In fact, it’s possible to trick people into logging out which falls under a “denial of service” attack. In an optimal case, we would need an id_token_hint with a valid id token to perform the log out.

Here are the OIDC drafts:

1 Like

There is one: https://www.ory.sh/docs/hydra/sdk/hydra-sdk-api#invalidates-a-user-s-authentication-session

This appears to be deleting authentication sessions across all devices. But I guess that’s an acceptable trade-off right now.

After a quick scooping of the oidc session drafts I couldn’t find guidance if this should be allowed or not. Maybe you can double-check?

I didn’t find anything either. They do mention how the post_logout_uri should work, but there is nothing about retaining dynamically-built query strings. Only those that are part of the pre-registered URL(s).

1 Like

Hi! First of all sorry to “reopen” this old topic but I’ve facing this very same problem during the last days:

Is there by chance any update on this? Currently the only option I see (as you mention here) is to revoke the user’s all active sessions, but it’s something that doesn’t fit very well in our project: we can have more than one session per users in different devices.

It would be nice to have an endpoint that allows revoking a single session from the login endpoint based on session-id for example.

@hackerman do you consider implementing this idea? Thanks in advance!

1 Like

Just redirect the browser back to the oauth2 url (you can get it from the login challenge) and add &prompt=login

2 Likes

It’s working, thank you very much!

To add just a bit of clarification that took me some time to find out. It seems setting the prompt=login does not only ask the identity provider to show the login, but also forgets a previous session. The later is the important part for us. In my scenario the identity provider authenticates the user by a token from an email link which is temporarily stored as a cookie at the identity provider and I do not show the login.