-
Notifications
You must be signed in to change notification settings - Fork 423
MSC2918: Refresh tokens #2918
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
MSC2918: Refresh tokens #2918
Changes from 8 commits
ab50b62
f8dad2a
0e615f7
870cded
6530ecc
b320001
d433e3b
87566c3
269fcac
4d73b7e
db8ceab
9bbb4c5
a050dc3
2c11e6f
4cd94e3
04ae1c3
488e9e1
4cf821c
c076763
a157cc3
ed54213
70b2dfc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| # MSC2918: Refresh tokens | ||
turt2live marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| Requests are currently authenticated using non-expiring, revocable access tokens. | ||
sandhose marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| This goes against security best practices known in the OAuth 2.0 world. | ||
| This MSC make the access tokens expiring and introduces refresh tokens to renew them to fight against token replay attacks. | ||
sandhose marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## Proposal | ||
|
|
||
| The access token returned by the login endpoint expires after a short amount of time, forcing the client to renew it with a refresh token. | ||
uhoreg marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| A refresh token is issued on login and rotates on each usage. | ||
|
|
||
| Homeservers can choose to make the access tokens signed and non-revocable for performance reasons if the expiration is short enough (less than 5 minutes). | ||
turt2live marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ### Login API changes | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| The login API returns two additional fields: | ||
clokep marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| - `expires_in_ms`: The lifetime in milliseconds of the access token. | ||
sandhose marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| - `refresh_token`: The refresh token, which can be used to obtain new access tokens. | ||
sandhose marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| This also applies to logins done by application services. | ||
|
|
||
| ### Account registration API changes | ||
|
|
||
| Unless `inhibit_login` is `true`, the account registration API returns two additional fields: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be if its false? I.e. we include the params when we return a valid access token?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The spec says:
So that sentence seems correct? If
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's not the easiest thing to grok, but @sandhose is right, and @erikjohnston is confused. |
||
|
|
||
| - `expires_in_ms`: The lifetime in milliseconds of the access token. | ||
| - `refresh_token`: The refresh token, which can be used to obtain new access tokens. | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the refresh token expire? How does one manually expire it?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also would be interested to see how this plays with soft logout: if the access token expires, but the refresh token is still live, should the server be using
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Refresh token don't expire, but they get invalidated on use.
I was not aware of how
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This particular piece is a bit concerning, as it means that refresh tokens are hanging around waiting to give access back to the account. On the other hand, this somewhat fixes the scripts usecase as it can then store the refresh token and use that on the next run.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It still heavily mitigates the impact of token leakage, since they are rotating. tl;dr: if there is an attempt to use an old refresh token, there might be a token leak somewhere and the whole session should be invalidated. This could be mentioned in the MSC and/or in the spec, and implemented in Synapse if you thing it makes sense.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also clarified about
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah ha, so the server would revoke the access token when a refresh token is used twice?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It could, although not strictly enforced by this MSC (and the current implementation in Synapse does not do that)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @turt2live is this clear enough now? |
||
|
|
||
| This also applies to registrations done by application services. | ||
sandhose marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ### Token refresh API | ||
|
|
||
| This API lets the client refresh the access token. | ||
| A new refresh token is also issued, and the existing one is revoked. | ||
| Since this request can get lost in flight, the server should delay this revocation to when the client uses the new access token or the new refresh token for the first time. | ||
richvdh marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
sandhose marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
turt2live marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| The Matrix server can but does not have to make the old access token invalid, since its lifetime is short enough. | ||
turt2live marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| `POST /_matrix/client/r0/refresh` | ||
turt2live marked this conversation as resolved.
Show resolved
Hide resolved
turt2live marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ```json | ||
| { | ||
| "refresh_token": "aaaabbbbccccdddd" | ||
| } | ||
| ``` | ||
|
|
||
| response: | ||
|
|
||
| ```json | ||
| { | ||
| "access_token": "xxxxyyyyzzz", | ||
| "expires_in_ms": 60000, | ||
| "refresh_token": "eeeeffffgggghhhh" | ||
| } | ||
| ``` | ||
|
|
||
| The `refresh_token` parameter can be invalid for two reasons: | ||
|
|
||
| - if it does not exist | ||
| - if it was already used once | ||
sandhose marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| In both cases, the server must reply with a `401` HTTP status code and an `M_UNKNOWN_TOKEN` error code. | ||
sandhose marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| This new use case of the `M_UNKNOWN_TOKEN` error code must be reflected in the spec. | ||
|
|
||
| ### Device handling | ||
|
|
||
| The current spec states that "Matrix servers should record which device each access token is assigned to". | ||
| This must be updated to reflect that devices are bound to a session, which are created during login and stays the same after refreshing the token. | ||
|
|
||
uhoreg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ## Potential issues | ||
|
|
||
| The refresh token being rotated on each refresh is strongly recommended in the OAuth 2.0 world for unauthenticated clients to avoid token replay attacks. | ||
| This can however make the deployment of CLI tools for Matrix a bit harder, since the credentials can't be statically defined anymore. | ||
| This is not an issue in OAuth 2.0 because usually CLI tools use the client credentials flow, also known as service accounts. | ||
| An alternative would be to make the refresh token non-rotating for now but recommend clients to support rotation of refresh tokens and enforce it later on. | ||
|
|
||
| ## Alternatives | ||
|
|
||
| This MSC defines a new endpoint for token refresh, but it could also be integrated as a new authentication mechanism. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. to expand on this, and the "potential issues" section above, what are the concerns with introducing it as some form of opt-in (or opt-out) mechanism for things like long-lived bots or scripts which do not easily have a refresh opportunity? For example, a nightly batch job to prune rooms/events/etc could use a static access token instead of having to login, do the work, then log out again, which would put the password near the script rather than a single revocable token.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think for both use cases (bots and scripts) I'd rather make use of the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair, that sounds reasonable. Just wanted to expand on the potential usecase, but agreed that scripts can find other ways to authenticate (or better yet: be replaced by features within the protocol/homeserver implementation)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think an authentication option for scripts needs to be in the spec. I have a lot of scripts that push notifications or upload files from CI jobs for example. Those use access tokens, because CI jobs do sometimes get compromised (happened once because of codecov) and that way the access token can be easily rotated without being a homeserver admin. If the script used username and password instead, an attacker would have been able to get past UIA and change the password and just in general do much more nasty stuff than with an access token. The jobs also can't refresh the access token, since they may be running concurrently and can't change CI variables. What would be my alternative for that use case, that works independent of the specific homeserver implementation?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is an ongoing effort to rework the whole authentication process, with use cases like scripts running in CI in mind. This MSC is also done to prepare clients for the eventual migration to this new authentication stack without having them to logout all their existing sessions. The login API with non-expiring token will hopefully stay until this new auth stack is ready, so when you would need to migrate you will have a proper alternative. In the meantime, if you want to still adopt refresh tokens and you are admin of your homeserver, I suggest you look into the |
||
|
|
||
| ## Security considerations | ||
|
|
||
| The time to live (TTL) of access tokens isn't enforced in this MSC but is advised to be kept relatively short. | ||
| Servers might choose to have stateless, digitally signed access tokens (JWT are good examples of this), which makes them non-revocable. | ||
| The TTL of access tokens should not exceed 15 minutes if they are revocable and 5 minutes if they are not. | ||
|
|
||
| ## Unstable prefix | ||
|
|
||
| While this MSC is not in a released version of the specification, clients should add a `org.matrix.msc2918.refresh_token=true` query parameter on the login endpoint, e.g. `/_matrix/client/r0/login?org.matrix.msc2918.refresh_token=true`. | ||
sandhose marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
richvdh marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| The refresh token endpoint should be served and used using the unstable prefix: `POST /_matrix/client/unstable/org.matrix.msc2918/refresh`. | ||
Uh oh!
There was an error while loading. Please reload this page.