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

Sessions don't refresh group membership information without logout/login cycle #81

Closed
jameshadfield opened this issue Jan 14, 2020 · 2 comments · Fixed by #537
Closed
Assignees
Labels
bug Something isn't working

Comments

@jameshadfield
Copy link
Member

jameshadfield commented Jan 14, 2020

Right now the code (as I understand it) fetches and stores cognito groups upon login (https://github.com/nextstrain/nextstrain.org/blob/master/authn.js#L76). This can lead to situations where (i) the code doesn't see "new" cognito groups until the user re-logs-in and (ii) the code sees groups which have been removed until the user logs in again.

@tsibley is this the general way things are done (this is not my area of expertise)? Could we poll the Cogntio API every ~24 hours to update without requiring the user to re-login?

@jameshadfield jameshadfield added the proposal Proposals that warrant further discussion label Jan 14, 2020
@tsibley
Copy link
Member

tsibley commented Jan 14, 2020

@jameshadfield The current behaviour is a temporary one, which arose from necessary expediency in the original manifestation the INRB group.

As I mentioned earlier, we can improve this lag/re-login issue in the future by refreshing user details from Cognito on ~every nextstrain.org request, ideally with a short-lived TTL cache in between to avoid multiple hits to Cognito across a rapid series of nextstrain.org requests.

@tsibley tsibley changed the title RFC: cognito group access & storage Sessions don't refresh group membership information without logout/login cycle Feb 11, 2022
@victorlin victorlin moved this from New to Prioritized in Nextstrain planning (archived) Feb 16, 2022
@tsibley tsibley added bug Something isn't working and removed proposal Proposals that warrant further discussion labels Feb 16, 2022
@tsibley
Copy link
Member

tsibley commented Feb 26, 2022

Recent thinking on two approaches that combined will address session staleness:

  1. Store the authn tokens in the session instead of storing the user info extracted from the initial login token (which is then discarded). Validate the stored token and extract the user info on each request, with automatic behind-the-scenes renewal by the server as necessary. This is identical to the Nextstrain CLI token handling, and will let us control how often the server refreshes info from Cognito by tuning the token lifetimes in the Cognito client config for the server.

  2. For group memberships specifically, we can track on the server side when things like group membership changes happen (once we extend our API to cover membership management) and record a timestamp that says "don't accept tokens created before X for user Y". This is a basic implementation of an authn revocation system. The system will then a) force a web session with a stale but not expired token to be renewed by the server (per above) and b) force API requests with a stale but not expired token to be retried after renewal by the API client (which means we'd want to implement this in Nextstrain CLI).

tsibley added a commit that referenced this issue Mar 18, 2022
This will make potential future user object updates in deserializeUser()
much easier because we won't have to figure out which old user
properties contain the "unparsed" or "original" array of Cognito groups
to re-parse.

Ultimately, it will still be better to move to storing the original id
and refresh tokens in the session instead of the user object.  The
deserializeUser() step would then reparse the id token (after
potentially refreshing it) every time and not have to worry about
upgrading a derived, persistent object.  I'm not sure when we'll get
around to that, however, so for now this bit of low-effort
future-proofing seems likely to be helpful.

Related to <#81>.
tsibley added a commit that referenced this issue Apr 7, 2022
This will make potential future user object updates in deserializeUser()
much easier because we won't have to figure out which old user
properties contain the "unparsed" or "original" array of Cognito groups
to re-parse.

Ultimately, it will still be better to move to storing the original id
and refresh tokens in the session instead of the user object.  The
deserializeUser() step would then reparse the id token (after
potentially refreshing it) every time and not have to worry about
upgrading a derived, persistent object.  I'm not sure when we'll get
around to that, however, so for now this bit of low-effort
future-proofing seems likely to be helpful.

Related to <#81>.
tsibley added a commit that referenced this issue Apr 7, 2022
This will make potential future user object updates in deserializeUser()
much easier because we won't have to figure out which old user
properties contain the "unparsed" or "original" array of Cognito groups
to re-parse.

Ultimately, it will still be better to move to storing the original id
and refresh tokens in the session instead of the user object.  The
deserializeUser() step would then reparse the id token (after
potentially refreshing it) every time and not have to worry about
upgrading a derived, persistent object.  I'm not sure when we'll get
around to that, however, so for now this bit of low-effort
future-proofing seems likely to be helpful.

Related to <#81>.
@tsibley tsibley self-assigned this May 5, 2022
tsibley added a commit that referenced this issue May 6, 2022
Partially resolves <#81>.
tsibley added a commit that referenced this issue May 10, 2022
…object

Validates the stored tokens and constructs a user object on each
request, with automatic behind-the-scenes token renewal (and thus user
info refresh) as necessary.  This is identical to the Nextstrain CLI
token handling.  We can control how often the server refreshes info from
Cognito by tuning the token lifetimes in the Cognito client config for
the server.  This change also lets us implement "stale before"
timestamps for users when we knowingly change their info in Cognito and
automatically refresh based on those so that we can keep the cached data
fresh.

Existing sessions are left as-is for now.  They can't be upgraded
automatically because the tokens used to establish them at initial login
are long gone.  We'll need to decide what to do with them (e.g. force
all existing sessions to login again).

Partially resolves <#81>.
tsibley added a commit that referenced this issue May 10, 2022
This is a basic cache-invalidation system applied to our authn tokens
(and future snapshots of user info).¹  When the app knowingly makes
changes to user data in Cognito, such as modifying a user's group
memberships, it can mark user data as "stale before" that time.  For
sessions, this triggers automatic server-side token renewal and thus
refresh of user data (e.g.  the new group memberships) from Cognito.
For API clients (like Nextstrain CLI), this triggers a 401 Unauthorized
error prompting the client to retry after renewal.

Timestamps are stored in Redis under a per-user key, if Redis is
available, else they're stored in a transient in-process Map (useful
only for dev).

Currently no code sets these timestamps.  I expect the first usage to be
with the addition of group membership management endpoints to the
RESTful API.

Partially resolves <#81>.

¹ Using JWTs means you're dealing with a cache (e.g. of the data in the
  token), even if you don't normally think about it that way.  While it
  might seem we could use the JWT to just get the username and always
  fetch the latest user info from Cognito's admin API, this isn't
  feasible because of the rate limits imposed by Cognito and the
  additional latency it would introduce.
tsibley added a commit that referenced this issue May 10, 2022
…object

Validates the stored tokens and constructs a user object on each
request, with automatic behind-the-scenes token renewal (and thus user
info refresh) as necessary.  This is identical to the Nextstrain CLI
token handling.  We can control how often the server refreshes info from
Cognito by tuning the token lifetimes in the Cognito client config for
the server.  This change also lets us implement "stale before"
timestamps for users when we knowingly change their info in Cognito and
automatically refresh based on those so that we can keep the cached data
fresh.

Existing sessions are left as-is for now.  They can't be upgraded
automatically because the tokens used to establish them at initial login
are long gone.  We'll need to decide what to do with them (e.g. force
all existing sessions to login again).

Partially resolves <#81>.
tsibley added a commit that referenced this issue May 10, 2022
This is a basic cache-invalidation system applied to our authn tokens
(and future snapshots of user info).¹  When the app knowingly makes
changes to user data in Cognito, such as modifying a user's group
memberships, it can mark user data as "stale before" that time.  For
sessions, this triggers automatic server-side token renewal and thus
refresh of user data (e.g.  the new group memberships) from Cognito.
For API clients (like Nextstrain CLI), this triggers a 401 Unauthorized
error prompting the client to retry after renewal.

Timestamps are stored in Redis under a per-user key, if Redis is
available, else they're stored in a transient in-process Map (useful
only for dev).

Currently no code sets these timestamps.  I expect the first usage to be
with the addition of group membership management endpoints to the
RESTful API.

Partially resolves <#81>.

¹ Using JWTs means you're dealing with a cache (e.g. of the data in the
  token), even if you don't normally think about it that way.  While it
  might seem we could use the JWT to just get the username and always
  fetch the latest user info from Cognito's admin API, this isn't
  feasible because of the rate limits imposed by Cognito and the
  additional latency it would introduce.
@tsibley tsibley mentioned this issue May 10, 2022
3 tasks
tsibley added a commit that referenced this issue May 16, 2022
…object

Validates the stored tokens and constructs a user object on each
request, with automatic behind-the-scenes token renewal (and thus user
info refresh) as necessary.  This is identical to the Nextstrain CLI
token handling.  We can control how often the server refreshes info from
Cognito by tuning the token lifetimes in the Cognito client config for
the server.  This change also lets us implement "stale before"
timestamps for users when we knowingly change their info in Cognito and
automatically refresh based on those so that we can keep the cached data
fresh.

Existing sessions are left as-is for now.  They can't be upgraded
automatically because the tokens used to establish them at initial login
are long gone.  We'll need to decide what to do with them (e.g. force
all existing sessions to login again).

Partially resolves <#81>.
tsibley added a commit that referenced this issue May 16, 2022
This is a basic cache-invalidation system applied to our authn tokens
(and future snapshots of user info).¹  When the app knowingly makes
changes to user data in Cognito, such as modifying a user's group
memberships, it can mark user data as "stale before" that time.  For
sessions, this triggers automatic server-side token renewal and thus
refresh of user data (e.g.  the new group memberships) from Cognito.
For API clients (like Nextstrain CLI), this triggers a 401 Unauthorized
error prompting the client to retry after renewal.

Timestamps are stored in Redis under a per-user key, if Redis is
available, else they're stored in a transient in-process Map (useful
only for dev).

Currently no code sets these timestamps.  I expect the first usage to be
with the addition of group membership management endpoints to the
RESTful API.

Partially resolves <#81>.

¹ Using JWTs means you're dealing with a cache (e.g. of the data in the
  token), even if you don't normally think about it that way.  While it
  might seem we could use the JWT to just get the username and always
  fetch the latest user info from Cognito's admin API, this isn't
  feasible because of the rate limits imposed by Cognito and the
  additional latency it would introduce.
Repository owner moved this from Prioritized to Done in Nextstrain planning (archived) Jun 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
No open projects
Development

Successfully merging a pull request may close this issue.

2 participants