From 1f8158715423b25ce3c8fb856e910c7afdac9aac Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Tue, 10 May 2022 15:09:27 -0400 Subject: [PATCH 1/5] add spec for refresh tokens --- content/client-server-api/_index.md | 99 ++++++++++++++++----- data/api/client-server/login.yaml | 25 ++++++ data/api/client-server/refresh.yaml | 107 +++++++++++++++++++++++ data/api/client-server/registration.yaml | 27 ++++++ 4 files changed, 235 insertions(+), 23 deletions(-) create mode 100644 data/api/client-server/refresh.yaml diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index 3edb5b16..52398c9c 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -71,7 +71,7 @@ These error codes can be returned by any API endpoint: Forbidden access, e.g. joining a room without permission, failed login. `M_UNKNOWN_TOKEN` -The access token specified was not recognised. +The access or refresh token specified was not recognised. An additional response parameter, `soft_logout`, might be present on the response for 401 HTTP status codes. See [the soft logout @@ -314,7 +314,8 @@ Most API endpoints require the user to identify themselves by presenting previously obtained credentials in the form of an `access_token` query parameter or through an Authorization Header of `Bearer $access_token`. An access token is typically obtained via the [Login](#login) or -[Registration](#account-registration-and-management) processes. +[Registration](#account-registration-and-management) processes. Access tokens +can expire; a new access token can be generated by using a refresh token. {{% boxes/note %}} This specification does not mandate a particular format for the access @@ -336,42 +337,92 @@ to prevent the access token being leaked in access/HTTP logs. The query string should only be used in cases where the `Authorization` header is inaccessible for the client. -When credentials are required but missing or invalid, the HTTP call will -return with a status of 401 and the error code, `M_MISSING_TOKEN` or -`M_UNKNOWN_TOKEN` respectively. +{{% changed-in v="1.3" %}} When credentials are required but missing or +invalid, the HTTP call will return with a status of 401 and the error code, +`M_MISSING_TOKEN` or `M_UNKNOWN_TOKEN` respectively. Note that an error code +of `M_UNKNOWN_TOKEN` could mean one of four things: + +1. the access token was never valid. +2. the access token has been logged out. +3. the access token has been [soft logged out](#soft-logout). +4. the access token [needs to be refreshed](#refreshing-access-tokens). + +When a client receives an error code of `M_UNKNOWN_TOKEN`, it should: + +- attempt to [refresh the token](#refreshing-access-tokens), if it has a refresh + token; +- if [`soft_logout`](#soft-logout) is set to `true`, it can offer to + re-log in the user, retaining any of the client's persisted + information; +- otherwise, consider the user as having been logged out. ### Relationship between access tokens and devices Client [devices](../index.html#devices) are closely related to access -tokens. Matrix servers should record which device each access token is -assigned to, so that subsequent requests can be handled correctly. +tokens and refresh tokens. Matrix servers should record which device +each access token and refresh token are assigned to, so that +subsequent requests can be handled correctly. When a refresh token is +used to generate a new access token and refresh token, the new access +and refresh tokens are now bound to the device associated with the +initial refresh token. By default, the [Login](#login) and [Registration](#account-registration-and-management) processes auto-generate a new `device_id`. A client is also free to generate its own `device_id` or, provided the user remains the same, reuse a device: in either case the client should pass the `device_id` in the request body. If the client sets the `device_id`, the server will -invalidate any access token previously assigned to that device. There is -therefore at most one active access token assigned to each device at any -one time. +invalidate any access and refresh tokens previously assigned to that device. + +### Refreshing access tokens + +{{% added-in v="1.3" %}} + +Access tokens can expire after a certain amount of time. Any HTTP +calls that use an expired access token will return with an error code +`M_UNKNOWN_TOKEN`, preferably with `soft_logout: true`. When a client +receives this error and it has a refresh token, it should attempt to +refresh the access token by calling `/refresh`. Clients can also +refresh their access token at any time, even if it has not yet +expired. If the token refresh succeeds, it should use the new token +for future requests, and can re-try previously-failed requests with +the new token. When an access token is refreshed, a new refresh token +may be returned; if a new refresh token is given, the old refresh +token will be invalidated, and the new refresh token should be used +when the access token needs to be refreshed. + +If the token refresh fails, then the client can treat it as a [soft +logout](#soft-logout), if the error response included a `soft_logout: +true` property, and attempt to obtain a new access token by re-logging +in. If the error response does not include a `soft_logout: true` +property, the client should consider the user as being logged out. + +Handling of clients that do not support refresh tokens is up to the +homeserver; clients indicate their support for refresh tokens by +including a `refresh_token: true` property in the request body of the +`/login` and `/register` endpoints. For example, homeservers may allow +the use of non-expiring access tokens, or may expire access tokens +anyways and rely on soft logout behaviour on clients that don't +support refreshing. ### Soft logout -When a request fails due to a 401 status code per above, the server can -include an extra response parameter, `soft_logout`, to indicate if the -client's persisted information can be retained. This defaults to -`false`, indicating that the server has destroyed the session. Any -persisted state held by the client, such as encryption keys and device -information, must not be reused and must be discarded. +A client can be in a "soft logout" state if the server requires +re-authentication before continuing, but does not want to invalidate +the client's session. That is, any persisted state held by the client, +such as encryption keys and device information, must not be reused and +must be discarded, and can be re-used if the client successfully +re-authenticates. -When `soft_logout` is true, the client can acquire a new access token by -specifying the device ID it is already using to the login API. In most -cases a `soft_logout: true` response indicates that the user's session -has expired on the server-side and the user simply needs to provide -their credentials again. +The server indicates that the client is in a soft logout state by +including a `soft_logout: true` parameter in an `M_UNKNOWN_TOKEN` +error response; the `soft_logout` parameter defaults to `false`. -In either case, the client's previously known access token will no -longer function. +A client that receives such a response can try to [refresh its access +token](#refreshing-access-tokens), if it has a refresh token +available. If it does not have a refresh token available, or +refreshing fails with `soft_logout: true`, the client can acquire a +new access token by specifying the device ID it is already using to +the login API. ### User-Interactive Authentication API @@ -1105,6 +1156,8 @@ errcode of `M_EXCLUSIVE`. {{% http-api spec="client-server" api="login" %}} +{{% http-api spec="client-server" api="refresh" %}} + {{% http-api spec="client-server" api="logout" %}} #### Login Fallback diff --git a/data/api/client-server/login.yaml b/data/api/client-server/login.yaml index 8865665f..3eb1e0d2 100644 --- a/data/api/client-server/login.yaml +++ b/data/api/client-server/login.yaml @@ -1,5 +1,6 @@ # Copyright 2016 OpenMarket Ltd # Copyright 2018 New Vector Ltd +# Copyright 2022 The Matrix.org Foundation C.I.C. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -133,6 +134,11 @@ paths: description: |- A display name to assign to the newly-created device. Ignored if `device_id` corresponds to a known device. + refresh_token: + type: boolean + description: |- + If true, the client supports refresh tokens. + x-addedInMatrixVersion: "1.3" required: ["type"] responses: @@ -142,6 +148,8 @@ paths: application/json: { "user_id": "@cheeky_monkey:matrix.org", "access_token": "abc123", + "refresh_token": "def456", + "expires_in_ms": 60000, "device_id": "GHTYAJCE", "well_known": { "m.homeserver": { @@ -163,6 +171,23 @@ paths: description: |- An access token for the account. This access token can then be used to authorize other requests. + refresh_token: + type: string + description: |- + A refresh token for the account. This token can be used to + obtain a new access token when it expires by calling the + `/refresh` endpoint. + x-addedInMatrixVersion: "1.3" + expires_in_ms: + type: integer + description: |- + The lifetime of the access token, in milliseconds. Once + the access token has expired a new access token can be + obtained by using the provided refresh token. If no + refresh token is provided, the client will need re-log in + to obtain a new access token. If not given, the client can + assume that the access token will not expire. + x-addedInMatrixVersion: "1.3" home_server: type: string description: |- diff --git a/data/api/client-server/refresh.yaml b/data/api/client-server/refresh.yaml new file mode 100644 index 00000000..abe8c8fb --- /dev/null +++ b/data/api/client-server/refresh.yaml @@ -0,0 +1,107 @@ +# Copyright 2022 The Matrix.org Foundation C.I.C. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Registration and Login API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/v3 +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/refresh": + post: + x-addedInMatrixVersion: "1.3" + summary: Refresh an access token + description: |- + Refresh an access token. Clients should use the returned access token + when making subsequent API calls, and store the returned refresh token + (if given) in order to refresh the new access token when necessary. + + After an access token has been refreshed, a server can choose to + invalidate the old access token immediately, or can choose not to, for + example if the access token would expire soon anyways. Clients should + not make any assumptions about the old access token still being valid, + and should use the newly provided access token instead. + + Note that this endpoint does not require authentication, since + authentication is provided via the refresh token. + + Application Service identity assertion is disabled for this endpoint. + operationId: refresh + parameters: + - in: body + name: body + required: true + schema: + type: object + example: { + "refresh_token": "some_token" + } + properties: + refresh_token: + type: string + description: The refresh token + responses: + 200: + description: A new access token and refresh token were generated. + examples: + application/json: { + "access_token": "a_new_token", + "expires_in_ms": 60000, + "refresh_token": "another_new_token" + } + schema: + type: object + properties: + access_token: + type: string + description: |- + The new access token to use. + refresh_token: + type: string + description: |- + The new refresh token to use when the access token needs to + be refreshed again. If not given, the old refresh token can + be re-used. + expires_in_ms: + type: integer + description: |- + The lifetime of the access token, in milliseconds. If not + given, the client can assume that the access token will not + expire. + required: + - access_token + 401: + description: |- + The provided token was unknown, or has already been used. + examples: + application/json: { + "errcode": "M_UNKNOWN_TOKEN", + "error": "Soft logged out", + "soft_logout": true + } + schema: + "$ref": "definitions/errors/error.yaml" + 429: + description: This request was rate-limited. + schema: + "$ref": "definitions/errors/rate_limited.yaml" diff --git a/data/api/client-server/registration.yaml b/data/api/client-server/registration.yaml index 810d8bc9..7e6a34cd 100644 --- a/data/api/client-server/registration.yaml +++ b/data/api/client-server/registration.yaml @@ -1,4 +1,5 @@ # Copyright 2016 OpenMarket Ltd +# Copyright 2022 The Matrix.org Foundation C.I.C. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -127,6 +128,11 @@ paths: returned from this call, therefore preventing an automatic login. Defaults to false. example: false + refresh_token: + type: boolean + description: |- + If true, the client supports refresh tokens. + x-addedInMatrixVersion: "1.3" responses: 200: description: The account has been registered. @@ -152,6 +158,27 @@ paths: An access token for the account. This access token can then be used to authorize other requests. Required if the `inhibit_login` option is false. + refresh_token: + type: string + description: |- + A refresh token for the account. This token can be used to + obtain a new access token when it expires by calling the + `/refresh` endpoint. + + Omitted if the `inhibit_login` option is false. + x-addedInMatrixVersion: "1.3" + expires_in_ms: + type: integer + description: |- + The lifetime of the access token, in milliseconds. Once + the access token has expired a new access token can be + obtained by using the provided refresh token. If no + refresh token is provided, the client will need re-log in + to obtain a new access token. If not given, the client can + assume that the access token will not expire. + + Omitted if the `inhibit_login` option is false. + x-addedInMatrixVersion: "1.3" home_server: type: string description: |- From d692062e3a7f1ddf2acf04480b85d39ca0890639 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Tue, 10 May 2022 15:17:09 -0400 Subject: [PATCH 2/5] add changelog --- changelogs/client_server/newsfragments/1056.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelogs/client_server/newsfragments/1056.feature diff --git a/changelogs/client_server/newsfragments/1056.feature b/changelogs/client_server/newsfragments/1056.feature new file mode 100644 index 00000000..2f8febb7 --- /dev/null +++ b/changelogs/client_server/newsfragments/1056.feature @@ -0,0 +1 @@ +Add refresh tokens, per [MSC2918](https://github.com/matrix-org/matrix-spec-proposals/pull/2918). From 91b2a49b6f5a912975b3492a4c7615f2245cbf1b Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Tue, 31 May 2022 16:19:15 -0400 Subject: [PATCH 3/5] apply suggestions from review, and other clarifications --- content/client-server-api/_index.md | 92 +++++++++++++++-------------- data/api/client-server/refresh.yaml | 3 + 2 files changed, 51 insertions(+), 44 deletions(-) diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index 5eb5f8b5..5f2740b9 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -337,15 +337,15 @@ to prevent the access token being leaked in access/HTTP logs. The query string should only be used in cases where the `Authorization` header is inaccessible for the client. -{{% changed-in v="1.3" %}} When credentials are required but missing or -invalid, the HTTP call will return with a status of 401 and the error code, -`M_MISSING_TOKEN` or `M_UNKNOWN_TOKEN` respectively. Note that an error code -of `M_UNKNOWN_TOKEN` could mean one of four things: +When credentials are required but missing or invalid, the HTTP call will +return with a status of 401 and the error code, `M_MISSING_TOKEN` or +`M_UNKNOWN_TOKEN` respectively. Note that an error code of `M_UNKNOWN_TOKEN` +could mean one of four things: 1. the access token was never valid. 2. the access token has been logged out. 3. the access token has been [soft logged out](#soft-logout). -4. the access token [needs to be refreshed](#refreshing-access-tokens). +4. {{< added-in v="1.3" >}} the access token [needs to be refreshed](#refreshing-access-tokens). When a client receives an error code of `M_UNKNOWN_TOKEN`, it should: @@ -377,52 +377,56 @@ invalidate any access and refresh tokens previously assigned to that device. {{% added-in v="1.3" %}} -Access tokens can expire after a certain amount of time. Any HTTP -calls that use an expired access token will return with an error code -`M_UNKNOWN_TOKEN`, preferably with `soft_logout: true`. When a client -receives this error and it has a refresh token, it should attempt to -refresh the access token by calling `/refresh`. Clients can also -refresh their access token at any time, even if it has not yet -expired. If the token refresh succeeds, it should use the new token -for future requests, and can re-try previously-failed requests with -the new token. When an access token is refreshed, a new refresh token -may be returned; if a new refresh token is given, the old refresh -token will be invalidated, and the new refresh token should be used -when the access token needs to be refreshed. +Access tokens can expire after a certain amount of time. Any HTTP calls that +use an expired access token will return with an error code `M_UNKNOWN_TOKEN`, +preferably with `soft_logout: true`. When a client receives this error and it +has a refresh token, it should attempt to refresh the access token by calling +[`/refresh`](#post_matrixclientv3refresh). Clients can also refresh their +access token at any time, even if it has not yet expired. If the token refresh +succeeds, the client should use the new token for future requests, and can +re-try previously-failed requests with the new token. When an access token is +refreshed, a new refresh token may be returned; if a new refresh token is +given, the old refresh token will be invalidated, and the new refresh token +should be used when the access token needs to be refreshed. -If the token refresh fails, then the client can treat it as a [soft -logout](#soft-logout), if the error response included a `soft_logout: -true` property, and attempt to obtain a new access token by re-logging -in. If the error response does not include a `soft_logout: true` -property, the client should consider the user as being logged out. +The old refresh token remains valid until the new access token or refresh token +is used, at which point the old refresh token is revoked. This ensures that if +a client fails to receive or persist the new tokens, it will still be able to +refresh them. -Handling of clients that do not support refresh tokens is up to the -homeserver; clients indicate their support for refresh tokens by -including a `refresh_token: true` property in the request body of the -`/login` and `/register` endpoints. For example, homeservers may allow -the use of non-expiring access tokens, or may expire access tokens -anyways and rely on soft logout behaviour on clients that don't -support refreshing. +If the token refresh fails and the error response included a `soft_logout: +true` property, then the client can treat it as a [soft logout](#soft-logout) +and attempt to obtain a new access token by re-logging in. If the error +response does not include a `soft_logout: true` property, the client should +consider the user as being logged out. + +Handling of clients that do not support refresh tokens is up to the homeserver; +clients indicate their support for refresh tokens by including a +`refresh_token: true` property in the request body of the +[`/login`](#post_matrixclientv3login) and +[`/register`](#post_matrixclientv3register) endpoints. For example, homeservers +may allow the use of non-expiring access tokens, or may expire access tokens +anyways and rely on soft logout behaviour on clients that don't support +refreshing. ### Soft logout A client can be in a "soft logout" state if the server requires -re-authentication before continuing, but does not want to invalidate -the client's session. That is, any persisted state held by the client, -such as encryption keys and device information, must not be reused and -must be discarded, and can be re-used if the client successfully -re-authenticates. +re-authentication before continuing, but does not want to invalidate the +client's session. The server indicates that the client is in a soft logout +state by including a `soft_logout: true` parameter in an `M_UNKNOWN_TOKEN` +error response; the `soft_logout` parameter defaults to `false`. If the +`soft_logout` parameter is omitted or is `false`, this means the server has +destroyed the session and the client should not reuse it. That is, any +persisted state held by the client, such as encryption keys and device +information, must not be reused and must be discarded. If `soft_logout` is +`true` the client can reuse any persisted state. -The server indicates that the client is in a soft logout state by -including a `soft_logout: true` parameter in an `M_UNKNOWN_TOKEN` -error response; the `soft_logout` parameter defaults to `false`. - -A client that receives such a response can try to [refresh its access -token](#refreshing-access-tokens), if it has a refresh token -available. If it does not have a refresh token available, or -refreshing fails with `soft_logout: true`, the client can acquire a -new access token by specifying the device ID it is already using to -the login API. +{{% changed-in v="1.3" %}} A client that receives such a response can try to +[refresh its access token](#refreshing-access-tokens), if it has a refresh +token available. If it does not have a refresh token available, or refreshing +fails with `soft_logout: true`, the client can acquire a new access token by +specifying the device ID it is already using to the login API. ### User-Interactive Authentication API diff --git a/data/api/client-server/refresh.yaml b/data/api/client-server/refresh.yaml index abe8c8fb..273e6d6a 100644 --- a/data/api/client-server/refresh.yaml +++ b/data/api/client-server/refresh.yaml @@ -42,6 +42,9 @@ paths: not make any assumptions about the old access token still being valid, and should use the newly provided access token instead. + The old refresh token remains valid until the new access token or refresh token + is used, at which point the old refresh token is revoked. + Note that this endpoint does not require authentication, since authentication is provided via the refresh token. From ef3df9d549c4f06f6744c18bb17927878a5dd0f8 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Thu, 2 Jun 2022 16:17:46 -0400 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> --- content/client-server-api/_index.md | 4 ++-- data/api/client-server/login.yaml | 2 +- data/api/client-server/refresh.yaml | 4 ++-- data/api/client-server/registration.yaml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index 5f2740b9..4f82c9a8 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -391,8 +391,8 @@ should be used when the access token needs to be refreshed. The old refresh token remains valid until the new access token or refresh token is used, at which point the old refresh token is revoked. This ensures that if -a client fails to receive or persist the new tokens, it will still be able to -refresh them. +a client fails to receive or persist the new tokens, it will be able to repeat +the refresh operation. If the token refresh fails and the error response included a `soft_logout: true` property, then the client can treat it as a [soft logout](#soft-logout) diff --git a/data/api/client-server/login.yaml b/data/api/client-server/login.yaml index 3eb1e0d2..d279445d 100644 --- a/data/api/client-server/login.yaml +++ b/data/api/client-server/login.yaml @@ -184,7 +184,7 @@ paths: The lifetime of the access token, in milliseconds. Once the access token has expired a new access token can be obtained by using the provided refresh token. If no - refresh token is provided, the client will need re-log in + refresh token is provided, the client will need to re-log in to obtain a new access token. If not given, the client can assume that the access token will not expire. x-addedInMatrixVersion: "1.3" diff --git a/data/api/client-server/refresh.yaml b/data/api/client-server/refresh.yaml index 273e6d6a..29013c15 100644 --- a/data/api/client-server/refresh.yaml +++ b/data/api/client-server/refresh.yaml @@ -45,8 +45,8 @@ paths: The old refresh token remains valid until the new access token or refresh token is used, at which point the old refresh token is revoked. - Note that this endpoint does not require authentication, since - authentication is provided via the refresh token. + Note that this endpoint does not require authentication via an + access token. Authentication is provided via the refresh token. Application Service identity assertion is disabled for this endpoint. operationId: refresh diff --git a/data/api/client-server/registration.yaml b/data/api/client-server/registration.yaml index 7e6a34cd..21fc1b84 100644 --- a/data/api/client-server/registration.yaml +++ b/data/api/client-server/registration.yaml @@ -173,7 +173,7 @@ paths: The lifetime of the access token, in milliseconds. Once the access token has expired a new access token can be obtained by using the provided refresh token. If no - refresh token is provided, the client will need re-log in + refresh token is provided, the client will need to re-log in to obtain a new access token. If not given, the client can assume that the access token will not expire. From 9bf02ada5509ccaa72c58db55c5b60f8025f9694 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Thu, 2 Jun 2022 16:19:11 -0400 Subject: [PATCH 5/5] don't need securityDefinitions since we don't use access token --- data/api/client-server/refresh.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/data/api/client-server/refresh.yaml b/data/api/client-server/refresh.yaml index 29013c15..da34070c 100644 --- a/data/api/client-server/refresh.yaml +++ b/data/api/client-server/refresh.yaml @@ -24,8 +24,6 @@ consumes: - application/json produces: - application/json -securityDefinitions: - $ref: definitions/security.yaml paths: "/refresh": post: