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). diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index da19a7de..4f82c9a8 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 @@ -338,40 +339,94 @@ 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. +`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. {{< 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: + +- 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`](#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. + +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 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) +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 -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 +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. 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. +information, must not be reused and must be discarded. If `soft_logout` is +`true` the client can reuse any persisted state. -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. - -In either case, the client's previously known access token will no -longer function. +{{% 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 @@ -1105,6 +1160,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..d279445d 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 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" 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..da34070c --- /dev/null +++ b/data/api/client-server/refresh.yaml @@ -0,0 +1,108 @@ +# 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 +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. + + 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 via an + access token. 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..21fc1b84 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 to 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: |-