ory / kratos

The most scalable and customizable identity server on the market. Replace your Homegrown, Auth0, Okta, Firebase with better UX and DX. Has all the tablestakes: Passkeys, Social Sign In, Multi-Factor Auth, SMS, SAML, TOTP, and more. Written in Go, cloud native, headless, API-first. Available as a service on Ory Network and for self-hosters.
https://www.ory.sh/?utm_source=github&utm_medium=banner&utm_campaign=kratos
Apache License 2.0
11.18k stars 958 forks source link

Privileged Session state should be included in `whoami` response #2633

Open zepatrik opened 2 years ago

zepatrik commented 2 years ago

Preflight checklist

Describe your problem

Clients currently have no easy way to determine whether a session is privileged.

From https://ory-community.slack.com/archives/C012RJ2MQ1H/p1659099978177669

  1. I am calling self-service/settings/browser
  2. I use the return value to get the flow id and the csrf token
  3. I call /self-service/settings with the flow id, the csrf token and a NEW PASSWORD
  4. I receive error session_refresh_required with a redirect_browser_to url
  5. I redirect to the specified url /self-service/login/browser which contains query params for refresh=true and a return_to url that is /self-service/settings
  6. I log in with the user's credentials with refresh true
  7. I call the /self-service/settings
  8. The PASSWORD doesn't seem to be updated

Also requested by @tc-amorevino

Describe your ideal solution

It would be nice for the UX to be able to see whether the Update Password field should be displayed, or instead a login?refresh=true flow should be initiated. A privileged_until timestamp will probably be better for the technical side than a boolean, because the client could preemptively trigger the re-login if the session would be non-privileged within a short time. It also means the response does not change over time (assuming the session is not revoked/prolonged).

Workarounds or alternatives

Determine the "privileged" status on the client side by knowing the privileged session lifespan.

Version

all

Additional Context

No response

brahmlower commented 2 years ago

I started working on implementing this feature this weekend. I've got a branch in progress with the implementation working, but it's taking longer than I anticipated to get the tests updated. I think I'm relatively close to getting the tests finished though- hopefully in the next day or two.

zepatrik commented 2 years ago

Awesome!

brahmlower commented 1 year ago

So I've hit some difficulties with this issue.

Background on the first implementation:

The PR I opened implemented this feature by including the PrivilegedUntil timestamp in the session model and persisting it to the database. In review, @aeneasr pointed out that if an admin set an unintentionally large privileged_session_max_age configuration and sessions were created while that config was present, there would be no way to automatically fix those sessions. We agreed we should implement this without persisting the PrivilegedUntil timestamp to the database.

Current plans:

For this revised implementation, I'm sticking with adding the field to the Session struct, but this time without a database column being declared, like so:

// A Session
//
// swagger:model session
type Session struct {
    ...

    // The Privileged Until Timestamp
    PrivilegedUntil time.Time `json:"privileged_until" faker:"time_type" db:"-"`

    ...

This seems like an unavoidable requirement, since the field needs to be defined here to be included in the generated openapi resources.

A newly instantiated session will have a PrivilegedUntil value of the time.Time zero value, so I'm looking for the ideal way to set the correct value. I believe adding pointer receiver to handle the logic is safest (similar to the existing SetSessionDeviceInformation and SetAuthenticatorAssuranceLevel functions), so I've added the following:

type privilegedMaxAgeProvider interface {
    SelfServiceFlowSettingsPrivilegedSessionMaxAge(ctx context.Context) time.Duration
}

func (s *Session) SetPrivilegedUntil(ctx context.Context, c privilegedMaxAgeProvider) {
    s.PrivilegedUntil = s.AuthenticatedAt.Add(c.SelfServiceFlowSettingsPrivilegedSessionMaxAge(ctx))
}

This function should be called anywhere that a session instance is being constructed, but I think that's okay because I believe sessions are only instantiated within the session/session.go and persistence/sql/persister_session.go files. The configuration is already provided to the Activate session function so there's no challenge there, but the config isn't being passed to the functions at the persistence layer, so I think each of the following functions in persister_session.go would need to take the config as an additional argument:

Does this seem like the right strategy for implementing this? I wanted to check before spending a lot of time updating functions to pass the config instance down to the persistence layer. πŸ˜…

brahmlower commented 1 year ago

Hey @zepatrik do you have any opinions or concerns with the implementation plan I described above?

jeremy-serenne commented 1 year ago

Hi ! This is exactly what we are looking for ! Do you know when this could be done ? @zepatrik

brahmlower commented 1 year ago

@jeremy-serenne same here 😁 and I'm happy to put the changes together, but I'd like to first confirm with the maintainers first that they're comfortable with the approach I've described above before I sync a lot time into the implementation.

zepatrik commented 1 year ago

Sorry, somehow missed the discussion here. Maybe it makes more sense to do this on the handler layer, as that means less piping. You can quite easily write tests for the handlers that ensure they all have the field set.

brahmlower commented 1 year ago

Sorry to be late with my reply as well πŸ˜› (it's like a game of sorry-tag)

I can take a look at implementing this at the handler layer- I really appreciate the guidance there πŸ‘ I have a lot of stuff going on for q4 though, so I won't have a chance to start this until around the start of December if that's alright?

zepatrik commented 1 year ago

In case someone needs this earlier, they (or we) can do it earlier πŸ˜‰

github-actions[bot] commented 2 weeks ago

Hello contributors!

I am marking this issue as stale as it has not received any engagement from the community or maintainers for a year. That does not imply that the issue has no merit! If you feel strongly about this issue

Throughout its lifetime, Ory has received over 10.000 issues and PRs. To sustain that growth, we need to prioritize and focus on issues that are important to the community. A good indication of importance, and thus priority, is activity on a topic.

Unfortunately, burnout has become a topic of concern amongst open-source projects.

It can lead to severe personal and health issues as well as opening catastrophic attack vectors.

The motivation for this automation is to help prioritize issues in the backlog and not ignore, reject, or belittle anyone.

If this issue was marked as stale erroneously you can exempt it by adding the backlog label, assigning someone, or setting a milestone for it.

Thank you for your understanding and to anyone who participated in the conversation! And as written above, please do participate in the conversation if this topic is important to you!

Thank you πŸ™βœŒοΈ