node-solid-server
node-solid-server copied to clipboard
Re-consider access token lifespan
At the moment NSS defines a default 14-day token lifespan: https://github.com/solid/oidc-op/blob/main/src/AccessToken.js#L7 Why re-consider?
- a long life for NSS access tokens affects the entire Solid ecosystem. If another server (e.g. ESS, CSS) wants to support NSS access tokens, it needs to accept tokens with a very long life.
- Bearer tokens (which is what we're talking about) are global in nature for Solid. They are extremely powerful and so any exfiltration can have significant negative consequences (i.e. a user loses data to a malicious actor). Reducing the lifespan of a global access token reduces risk for everyone
- for access tokens (especially global access tokens) the industry standard is <= 1 hour. For access tokens that secure "sensitive" data, the standard is closer to 10 minutes or less
Suggestions:
- Request a refresh token during the authorization code flow (this is part of OpenID Connect). Then, when the token is nearing expiry, exchange the refresh token for a new access token
- Completely refresh the app, passing the user through the full authorization code flow (part of OIDC). There will still be a cookie available on the identity provider, so the user will, in all likelihood, be passed right through the redirect flow and end up where they started. There are patterns for maintaining state between the start and end of that flow
- If the cookie expired on the Identity Provider, then the user would need to re-enter credentials
- NSS uses cookies so we could exchange the access token with a cookie. Example: Gmail uses cookies. Cookies are easy to scope to a particular app, so there are no significant security issues.
What happens with ESS at the moment (say with the Pod Browser)? @acoburn
At the moment, in order to support access tokens generated by NSS, an ESS instance must accept access tokens with a 14-day lifespan. But an operator of ESS may choose to restrict this to more common industry standards, such as 1 hour. In fact, if an instance of ESS (or any Solid server implementation) contains sensitive or private data, it is quite likely that an operator would choose to restrict access tokens to a shorter maximum lifespan, which would necessarily cause all NSS access tokens with this default lifespan to be rejected.
In this context it is important to distinguish between what a user can do with a global access/ID tokens and what they can do with application-scoped session tokens, such as cookies. A global access token is extremely powerful. It allows a user in possession of one to do anything in the entire Solid ecosystem for as long as it is valid. That means that any exfiltration of the tokens is potentially catastrophic.
There are two ways the power of these tokens is constrained: (1) binding these tokens to keypairs, i.e. DPoP and (2) limiting the lifespan of a token.
Even with DPoP, if both the access token and the DPoP proof are exfiltrated from an application, a malicious user can replay that token/proof combination against a single endpoint repeatedly, provided that a server does not enforce jti claim uniqueness. In that case, a shorter lifespan of an access token becomes vitally important.
I see less of a problem with long-lived access tokens if those tokens are scoped to a particular Pod and if those tokens are not accessibly to JavaScript (i.e. httpOnly cookies), especially if that Pod is not storing sensitive data. But expecting that every Solid server will accept access tokens of arbitrary lifespans will likely not pass a rigorous security review.
@acoburn - thanks for the helpful explanation. What is a more reasonable expiration time? Is there a process for automatically refreshing the token on expiration that is missing from NSS?
What is a more reasonable expiration time?
A common value that I see is 1 hour (e.g. Google, Auth0, Cognito). CSS also issues tokens with a 1 hour lifespan. ESS issues tokens with a 30 minute lifespan. I also see tokens with a shorter lifespan, down to 5 minutes, especially in cases where sensitive user data is present. Outside of NSS, I don't believe I have ever encountered a system in the wild that issues access tokens with a lifespan > 1 hour.
Is there a process for automatically refreshing the token on expiration that is missing from NSS?
I can't speak to what may or may not be missing in NSS, but there are several standard OAuth2/OpenID ways that tokens can be refreshed:
- Use refresh tokens: when requesting the
offline_scopeat an OpenID authorization_endpoint, a client app can send the refresh token to the identity provider's token_endpoint to request that a new access token be issued. - Redirect the user through the authorization_code flow. There is typically some type of state that will need to be restored in this flow; for that, an app can place the state in session storage (or another convenient location) and use that state to restore data for the user. Alternatively, this state can be placed in the
stateparameter of the OpenID interaction
(historically, browsers would use an iframe-based approach, but recent browser security changes have made that approach no longer workable)
All things considered, the better approach IMO is to use refresh tokens as that is much less disruptive to how users interact with browser-based apps. The browser refresh flow requires navigating away from the active page and if a user was, for example, typing into a text box, it would be a very non-optimal user experience.
An alternative to the above is to fetch a global access/ID token from an identity provider and then exchange that token for an application- or pod-specific token, which could be a cookie. That pattern is also in line with some ongoing conversations in the Solid Authentication panel about scoping access tokens to particular resource (i.e. Pod) servers.
@acoburn I'm a bit embarrassed to ask but do you mean scope=offline_access?
Yes, offline_access. I should have checked the spec text :-)