> **Important**: The Real-Time Collaboration beta is invite only. For information on joining the beta, visit the [RTC beta access page]({{site.baseurl}}/rtc/beta-access/).
@ -6,21 +6,16 @@ description: Beta specific access and setup
keywords: rtc
---
> **Important**: The Real-Time Collaboration beta is invite only.
## Joining the beta
To register your interest in the beta program for {{site.productname}} Real-Time Collaboration (RTC), join the {{site.betaprogram}}. {{site.companyname}} will contact you with further details.
To join the {{site.betaprogram}}, visit the [{{site.betaprogram}}]({{site.betaprogramurl}}).
{% assign beta_feature = "The RTC plugin" %}
{% assign pre-release_type = "Open Beta" %}
{% include misc/beta-note.md %}
## Accessing the RTC beta plugin
Once you have been invited into the RTC beta program, to try the RTC beta plugin:
To try the RTC beta plugin:
* Set up a {{site.accountpage}}.
* Configure {{site.productname}} to use the {{site.cloudname}} development channel.
* Configure {{site.productname}} to use the {{site.cloudname}} RTC channel.
* Add the RTC plugin and configuration options to {{site.productname}}.
### Set up a {{site.accountpage}}
@ -29,12 +24,12 @@ The RTC beta plugin is only available for {{site.cloudname}} deployments and req
To create a {{site.accountpage}}, visit the [{{site.accountpage}} sign-up page]({{site.accountsignup}}).
### Configure {{site.productname}} to use the {{site.cloudname}} development channel
### Configure {{site.productname}} to use the {{site.cloudname}} RTC channel
To access the {{site.cloudname}} development channel (`5-dev`), source {{site.productname}} using the following script:
To access the {{site.cloudname}} RTC channel (`5-rtc`), source {{site.productname}} using the following script:
Replace `no-api-key` in the source script (`<scriptsrc=...`)witha{{site.accountpage}}APIkey,whichiscreatedwhensigningupto [{{site.accountpage}}]({{site.accountsignup}}).
@ -6,363 +6,365 @@ description: List of all available RTC configuration options.
keywords: rtc configuration
---
{% include misc/beta-note-rtc.md %}
> **Caution**: These configuration options are subject to change based on customer feedback. API compatibility is not guaranteed during the beta.
> **Note**: These configuration options is subject to change based on customer feedback.
## Configuration style
## Required options
The RTC plugin uses promise-based "provider" functions to support a variety of configuration scenarios including asynchronously fetching data from a server. Function input parameters are provided as an object, allowing unused fields to be omitted.
The following options are required for the RTC plugin:
For an example minimum configuration, see: [Examples of the minimum required configuration for the RTC plugin](#examplesoftheminimumrequiredconfigurationforthertcplugin).
The document details include a document ID. This is the ID users share with other people collaborating on the document.
### `rtc_document_id`
**Type:** `Function`
The RTC plugin requires a unique identifier for editor content to enable collaboration, known as the document ID. The identifier set by the integrator is used by the RTC server as a permanent reference for the content. {{site.companyname}} recommends using the same unique ID used by your server where possible, such as the unique page or document ID from a CMS.
**Required:** yes
> **Warning**: Do not reuse the document ID for different documents, otherwise content will be overwritten. Each document must have a unique identifier.
### Return fields for `rtc_document_details_provider`
| Field | Type | Description |
|-------|:----:|-------------|
| `documentId` | `string` | Unique identifier for the document. In a content management system, this could be the resource identifier for the document. |
When a client (user) connects:
* If the document ID already exist, the most recent version of the content is sent to the client's editor.
* If the document ID does not exist, the client uploads new initial content as the first version of that document ID.
### Example Using the document details provider to return static document details
> **Warning**: If the content is changed outside of an RTC session, a new document ID must be generated. Changes made outside the RTC session will be overwritten by the content on the RTC server during the next collaboration session.
```js
tinymce.init({
selector: 'textarea', // change this value according to your HTML
### Example Using the document details provider to return document details from your server
**Required:** yes
```js
tinymce.init({
selector: 'textarea', // change this value according to your HTML
plugins: 'rtc',
rtc_document_details_provider: () => {
return fetch('http://yourserver/getDocumentId', {
method: 'POST'
})
.then(documentId => ({ documentId });
}
})
```
### `rtc_encryption_provider`
## `rtc_encryption_provider`
The RTC plugin requires an encryption key for end-to-end encryption. This key is not sent to the {{site.cloudname}} server; the {{site.productname}} RTC service cannot read the editor content. The encryption key is used by the browser's [SubtleCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) API to encrypt and decrypt editor content on the client.
The RTC plugin requires a generated key to encrypt messages for end-to-end encryption. This key not sent to the {{site.cloudname}}, preventing the {{site.productname}} RTC service from reading the editor content. The encryption key is used by the user's browser to encrypt and decrypt the editor content between users.
A key is required when the client needs to:
1. Encrypt new data in the collaboration session.
2. Read previously written data from the collaboration session.
To generate a unique encryption key, create a function that accepts the following arguments:
* The document ID
* The session ID
* A key hint (only required for existing sessions)
Suggestions on how to generate a secure encryption key and participate in key rotation are available in [Generating a secure encryption key]({{site.baseurl}}/rtc/jwt-authentication/#generatingasecureencryptionkey). To help with this process a customisable "key hint" is available.
Secure ways to generate an encryption key include:
#### Key Hint
* Generate and store a random key for each new document session in your database.
* Generate single random key per document, and salt it _on your server_ with the session ID to provide a different key per session.
* Store a global list of keys for your application, and use the document ID and session ID to salt the current key _on your server_ to produce a key unique to the document session. You should return a key hint to identify the key in case you rotate to a new key later.
If keys are never rotated this can be ignored. For advice on how to use the key hint to rotate encryption keys, see [Generating a secure encryption key]({{site.baseurl}}/rtc/jwt-authentication/#generatingasecureencryptionkey).
**Type:** `Function`
**Required:** yes
### Input fields for `rtc_encryption_provider`
#### Input fields for `rtc_encryption_provider`
| Field | Type | Description |
|-------|:----:|-------------|
| `documentId` | `string` | Unique public document ID that is used when sharing the document. |
| `sessionId` | `integer` | Session ID generated by the server. This should be a number between 0 and 2147483648 (2<sup>31</sup>). |
| `keyHint` | `string` or `undefined` | Key hint (such as a key ID) provided by the client which opened the session (only required if there is an existing session). |
| `documentId` | `string` | The document ID from the [`rtc_document_id`](#rtc_document_id) option
| `keyHint` | `string` or `null` | Key hint returned by the client which opened the session, if connecting to an existing session. |
### Return fields for `rtc_encryption_provider`
#### Return fields for `rtc_encryption_provider`
| Field | Type | Description |
|-------|:----:|-------------|
| `key` | `string` | Encryption key that is used to locally encrypt operations. This key needs to be the same for all connecting clients. |
| `keyHint` | `string` or `undefined` | Optional key hint to provide to clients that connect later, to aid in key selection. This can be a key thumbprint, ID or other non-sensitive identifier that will help select the key like a timestamp. It is only recorded when opening a new session. (unicode, max 256 characters) |
### Example of providing static encryption details
```js
tinymce.init({
selector: 'textarea', // change this value according to your HTML
| `key` | `string` | Encryption key that is used to locally encrypt operations. This key needs to be the same for all connecting clients on the same session. |
| `keyHint` | `string` | Key hint to provide to future clients to aid in key selection. If keys are never rotated, it can be a fixed arbitrary value. It is only recorded when the input `keyHint` is `null`. (unicode, max 256 characters) |
### Example of providing encryption details from your server
### `rtc_token_provider`
```js
tinymce.init({
selector: 'textarea', // change this value according to your HTML
The RTC plugin and the RTC service uses [JSON Web Tokens (JWT)]({{site.baseurl}}/rtc/jwt-authentication/) to authenticate the user. The user's token should include:
## `rtc_token_provider`
* A unique user ID,
* A relative expiration time.
The RTC plugin and service uses [JWT]({{site.baseurl}}/rtc/jwt-authentication/) to authenticate the user. This token should include a unique user ID and a relative expiration time. This provider function will be called multiple times to refresh the token if it's about to expire. For production usage, the token provider should be a dynamic request that produces a new JWT token with an updated `exp` claim.
The `rtc_token_provider` function will be called one or more times to refresh the token before it expires. For production usage, {{site.companyname}} recommends a token provider with a dynamic request that produces a new JWT token with an updated `exp` claim.
**Type:** `Function`
**Required:** yes
### Required JWT claims
#### Required JWT claims
| Field | Type | Description |
|-------|:----:|-------------|
| `sub` | `string` | The unique user ID (If `sub` is the same for two clients, the server should trust them as if they are the same user). |
| `sub` | `string` | The unique user ID (If `sub` is the same for two clients, the server will trust them as if they are the same user). |
| `exp` | `integer` | The timestamp when the token expires. |
### Return fields for `rtc_token_provider`
#### Return fields for `rtc_token_provider`
| Field | Type | Description |
|-------|:----:|-------------|
| `token` | `string` | A generated JWT token. This token should be signed with the private key. |
| `token` | `string` | A generated JWT token. This token should be signed with a private key as described in [JWT authentication]({{site.baseurl}}/rtc/jwt-authentication/#privatepublickeypairsfortinycloudservices). |
### Examples of the minimum required configuration for the RTC plugin
### Example of providing a static JWT token
#### Static configuration
```js
tinymce.init({
selector: 'textarea', // change this value according to your HTML
selector: 'textarea', // change this value according to your HTML
Real-time collaboration integrations regularly store the content, eliminating the need for a save button. The {{site.productname}} RTC plugin provides a version number to assist with storing the regular content snapshots. These snapshots are not stored by the {{site.cloudname}} and must be handled by the integrator.
For any given document ID, the server guarantees the version number will only increase. It can be safely used for conflict resolution. For each document ID and version combination the snapshot content is guaranteed to be identical.
The snapshot callback will be executed at regular intervals with access to the serialized editor content. The content is retrieved through a `getContent` function to reduce CPU load if the callback decides to not use the editor content.
Real-time collaboration sessions don't typically have a save button and the session is constantly being stored. A snapshot callback will be executed at regular intervals with the serialized editor content. The content is retrieved though a `getContent` function to lazily create the serialized version of the content to reduce the CPU load.
**Type:** `Function`
**Required:** no
### Input fields for `rtc_snapshot`
#### Input fields for `rtc_snapshot`
| Field | Type | Description |
|-------|:----:|-------------|
| `sessionId` | `integer` | Session identifier generated by the server. This should be a number between 0 and 2147483648 (2<sup>31</sup>). |
| `version` | `integer` | An increasing version number between 0 and 2147483648 (2<sup>31</sup>). |
| `documentId` | `string` | The document ID from the [`rtc_document_id`](#rtc_document_id) option
| `version` | `integer` | An increasing version number, specific to the current document ID, between 0 and 2147483648 (2<sup>31</sup>). |
| `getContent()` | `string` | Function to execute to get the content for that particular version. |
### Example of getting content snapshots
#### Example of getting content snapshots
```js
tinymce.init({
selector: 'textarea', // change this value according to your HTML
selector: 'textarea', // change this value according to your HTML
By default, the initial editor content is retrieved from the element targeted using the {{site.productname}} [`selector` option]({{site.baseurl}}/configure/integration-and-setup/#selector).
By default, the initial editor content is retrieved from the target element, as specified using the `selector` option. The RTC plugin provides the `rtc_initial_content_provider` option to allow the initial content be retrieved for a new RTC session. This also works with the various {{site.productname}} [integrations]({{site.baseurl}}/integrations/) that don't provide access to the target element directly.
The `rtc_initial_content_provider` option allows alternative initial content be retrieved for a new RTC session. This option works with frameworks and integrations (such as the {{site.productname}} [integrations]({{site.baseurl}}/integrations/)) that don't provide access to the target element directly.
**Type:** `Function`
**Required:** no
### Return fields for `rtc_initial_content_provider`
### Input fields for `rtc_initial_content_provider`
| Field | Type | Description |
|-------|:----:|-------------|
| `documentId` | `string` | The document ID configured using the `rtc_document_id` option.
### Fields to return from `rtc_initial_content_provider`
| Field | Type | Description |
|-------|:----:|-------------|
| `content` | `string` | String containing the HTML to be imported into the editor when there is no active session. |
### Example of providing static content
#### Example of providing static content
```js
tinymce.init({
selector: 'textarea', // change this value according to your HTML
selector: 'textarea', // change this value according to your HTML
### Example of providing dynamic content from the server
#### Example of providing dynamic content from the server
```js
tinymce.init({
selector: 'textarea', // change this value according to your HTML
selector: 'textarea', // change this value according to your HTML
plugins: 'rtc',
rtc_initial_content_provider: () => {
return fetch('http://yourserver/getContent', {
rtc_initial_content_provider: ({documentId}) => {
return fetch(`/getContent/${documentId}`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
}).then(response => response.json());
}
})
```
## `rtc_user_details_provider`
### `rtc_user_details_provider`
By default, a user's unique ID (the `sub` field from their [JWT](#rtc_token_provider)) will be displayed as their name in remote caret tooltips.
To display a descriptive name on the caret, the user ID needs to be resolved into user details that include the full user name. This resolution is done on each client to avoid sending any personal information through the RTC server.
By default, the user ID will be displayed as the name on remote carets shown in the editor when there are active collaborators on the editor content. To display an alternative name on the caret, the user ID needs to be resolved into user details that include the full user name. This provider function will be called for each connecting client. If this option is omitted, the user ID will be presented as the name for the remote user, when a user hovers over the remote carets.
Only the full name is required, but the whole object will be cloned and stored within the RTC caret information. It will then be included with both [`rtc_client_connected`](#rtc_client_connected) and [`rtc_client_disconnected`](#rtc_client_disconnected) callbacks so that the lookup does not need to be repeated.
Only `userId` is guaranteed to be authentic as it comes from the JWT. {{site.companyname}} suggests using `userId` to fetch the user data from your server to guarantee authenticity.
This provider function will be called once for each connecting client. Clients that reconnect may trigger a new call to the provider function rather than using cached data.
**Type:** `Function`
**Required:** no
### Input fields for `rtc_user_details_provider`
#### Input fields for `rtc_user_details_provider`
| Field | Type | Description |
|-------|:----:|-------------|
| `userId` | `string` | User ID to resolve into user details. |
### Return fields for`rtc_user_details_provider`
#### Fields to return from`rtc_user_details_provider`
| Field | Type | Description |
|-------|:----:|-------------|
| `fullName` | `string` | Full name of user. For example: `"John Doe"`. |
| any custom field | `any` | Extra user data for use in the [`rtc_client_connected` API](#rtc_client_connected). |
### Example of providing static user details
#### Example of providing static user details
```js
tinymce.init({
selector: 'textarea', // change this value according to your HTML
selector: 'textarea', // change this value according to your HTML
The `rtc_custom_user_details` option allows extra details to be provided about the current user to the other connecting clients. This option should not be used to communicate sensitive information; the authenticity of user data cannot not guaranteed.
The `rtc_client_info` option allows status flags from the local editor environment to be provided to other connecting clients, for example "is the user on a mobile device". This option should not be used to communicate sensitive information; the authenticity of the data cannot be guaranteed.
This API is designed for use in status flags such as "is the user on a mobile device".
This option accepts an object that must be serializable (`JSON.stringify` will be used to transmit it between clients).
This option accepts an object that must be serializable (`JSON.stringify` will be used to transmit it between clients). Other clients receive a copy of this object in their [`rtc_client_connected`](#rtc_client_connected) events.
**Type:** `Object`
**Required:** no
### Example of providing custom user details
#### Example of client status information
```js
tinymce.init({
selector: 'textarea', // change this value according to your HTML
selector: 'textarea', // change this value according to your HTML
This option allows applications to show when a user enters the RTC session. There are 8 distinct caret colors (given a value from 1 to 8). If more than 8 people connect to a session, the numbers will be reused. This is useful for keeping a list or array of connected users up-to-date.
This option allows applications to show when a user enters the RTC session. When used in combination with [`rtc_client_disconnected`](#rtc_client_disconnected), a user interface of connected users can be kept up to date.
**Type:** `Function`
Only one `rtc_client_connected` event will be fired per client connection. Connecting to a session with multiple existing clients will fire separate `rtc_client_connected` events for each existing client.
**Required:** no
To help with generating a user interface for connected users, four pieces of data are provided:
### Input fields for `rtc_user_connected`
#### User ID
| Field | Type | Description |
|-------|:----:|-------------|
| `userId` | `string` | This is the JWT user ID of the connecting user. |
| `caretNumber` | `integer` | The user's caret number 1-8 (helpful to disambiguate when a user connects multiple times). |
| `custom` | `object` | Custom data passed out from the other clients `rtc_custom_user_details` function. If none are provided, this will be an empty object. |
This is the user's unique ID (the `sub` field from their [JWT](({{site.baseurl}}/rtc/jwt-authentication/)), which is also used for [`rtc_user_details_provider`](#rtc_user_details_provider)). Multiple connection events will be received with the same user ID if a user opens multiple sessions (for example on desktop and mobile).
### Example of providing custom user details for `rtc_user_connected`
#### User Details
```js
tinymce.init({
selector: 'textarea', // change this value according to your HTML
This is a copy of the object returned by [`rtc_user_details_provider`](#rtc_user_details_provider). RTC only uses the `fullName` field, but the entire object will be cloned and passed to `rtc_client_connected`.
#### Client ID
This is a unique identifier, generated by the RTC protocol, that can be used to differentiate between the same user connecting multiple times.
#### Caret Number
This number corresponds to one of [the 8 colours defined in TinyMCE CSS](https://github.com/tinymce/tinymce/blob/master/modules/oxide/src/less/theme/content/rtc/rtc.less#L1-L8). TinyMCE supports 8 distinct caret colors. If more than 8 clients connect to a session, the numbers will be reused.
A custom skin is required to change these colours, and no more than 8 are supported in this release. For information on creating a custom skin, see: [Customizing the Editor UI]({{site.baseurl}}/general-configuration-guide/customize-ui/).
## `rtc_user_disconnected`
#### Client information
This option allows applications to track when a user leaves the session. This is useful for keeping a list or array of connected users up-to-date.
This is a copy of the [`rtc_client_info`](#rtc_client_info) data from the remote user's editor configuration.
> **Caution**: {{site.productname}} cannot guarantee the accuracy of client information data, which comes from a remote object. {{site.companyname}} recommends only using the client information data for status flags. To obtain authentic client information, use the [`rtc_user_details_provider`](#rtc_user_details_provider) data returned through the `userDetails` field.
**Type:** `Function`
**Required:** no
### Input fields for `rtc_user_disconnected`
#### Input fields for `rtc_client_connected`
| Field | Type | Description |
|-------|:----:|-------------|
| `userId` | `string` | This is the unique user ID of the disconnecting user. |
| `caretNumber` | `integer` | The user's caret number 1-8 (helpful to disambiguate when a user connects multiple times). |
| `custom` | `object` | Custom data passed out from the other clients `rtc_custom_user_details` function. If none are provided, this will be an empty object. |
| `userId` | `string` | The user's unique ID. |
| `userDetails` | `object` | User details object. If none is configured, this will be an empty object. |
| `clientId` | `string` | The unique ID for the new client connection |
| `caretNumber` | `integer` | The user's caret number (1-8). |
| `clientInfo` | `object` | Additional client information. If none was configured, this will be an empty object. |
### `rtc_client_disconnected`
The `rtc_client_disconnected` option can be used with the [`rtc_client_connected`](#rtc_client_connected) option to maintain a list of connected users.
**Type:** `Function`
**Required:** no
#### Input fields for `rtc_client_disconnected`
The same as [`rtc_client_connected`](#rtc_client_connected)
### Example of providing custom user details for `rtc_user_disconnected`
### Example using client connect and disconnect events with custom user details
```js
tinymce.init({
selector: 'textarea', // change this value according to your HTML
selector: 'textarea', // change this value according to your HTML
This event is fired when a user leaves the session.
### Event fields: `RtcUserDisconnected`
### Event fields: `RtcClientDisconnected`
| Field | Type | Description |
|-------|:----:|-------------|
| `userId` | `string` | Unique user ID of the disconnecting user. |
| `caretNumber` | `integer` | Caret number assigned to the user. |
| `custom` | `object` | Custom data object provided by the `rtc_custom_user_details` option, sent from the disconnecting user client. |
This event provides the same event fields as the `RtcClientConnected` event. See the [`rtc_client_connected`]({{site.baseurl}}/rtc/configuration#rtc_client_connected) documentation for a full description of the event fields.
### Example of using the RtcUserDisconnected event
### Example of using the RtcClientDisconnected event
```js
tinymce.init({
selector: 'textarea', // change this value according to your HTML
@ -6,33 +6,34 @@ description: Getting started with RTC
keywords: rtc
---
{% include misc/beta-note-rtc.md %}
{% assign beta_feature = "The RTC plugin" %}
{% assign pre-release_type = "Open Beta" %}
{% include misc/beta-note.md %}
## Prerequisites
* A {{site.cloudname}} API key from [{{site.accountpage}}]({{site.accountsignup}}).
* A JSON Web Token (JWT) key generated at [{{site.accountpageurl}}]({{site.accountpageurl}}). Temporarily store the generated keys for use in the following procedure.
* A [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) for your web server. RTC makes use of [SubtleCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) which is only available in secure contexts.
## Basic RTC setup
The following example shows how to get the RTC plugin up and running using hardcoded values. For a production setup, the document ID and secret would be retrieved from your server. This example also assumes that a JWT provider endpoint exists at '/jwt'. For information on setting up a JWT endpoint, see: [JWT authentication]({{site.baseurl}}/rtc/jwt-authentication/).
The following example assumes you are familiar with integrating TinyMCE; if you are new TinyMCE user please follow the [basic setup guide]({{site.baseurl}}/general-configuration-guide/basic-setup/) first.
This example shows how to get the RTC plugin up and running using hardcoded values. For a production setup, the document ID and secret would be retrieved from your server. This example also assumes that a JWT provider endpoint exists at '/jwt'. For information on setting up a JWT endpoint, see: [JWT authentication]({{site.baseurl}}/rtc/jwt-authentication/).
@ -6,12 +6,14 @@ description: Introduction of what RTC is and its capabilities
keywords: rtc introduction overview
---
{% include misc/beta-note-rtc.md %}
## Introduction
{% include misc/rtc-description.md %}
{% assign beta_feature = "The RTC plugin" %}
{% assign pre-release_type = "Open Beta" %}
{% include misc/beta-note.md %}
## JSON Web Token based authentication
Some cloud services for {{site.productname}} require setting up JSON Web Token (JWT) authentication. JWTs are a common solution for communicating user authorization with web services. JWTs are used to communicate to {{site.productname}} that the user has been authorized to access {{site.cloudname}} services.
@ -22,11 +24,11 @@ For information on using JWT authentication with the RTC plugin, see: [JWT authe
## Presence API
The RTC plugin comes with a presence API that enables tracking when users enter/leave the session. The only user information shared over the wire is the user id stored in the JWT `sub` claim. Other details such as the user's full name is resolved locally so the {{site.cloudname}} will never see who is actually connecting. There are currently 8 distinct caret colors which can be assigned to a connecting user. More than 8 users can still connect, but then the colors will be reused.
The RTC plugin exports a presence API to enable tracking when users enter and leave the collaboration session. The only user information shared through the RTC server is the user id stored in the JWT `sub` claim. Other details such as the user's full name are resolved locally so the {{site.cloudname}} will never see who is actually connecting. User resolution is performed through the [`rtc_user_details_provider` option]({{site.baseurl}}/rtc/configuration#rtc_user_details_provider). Presence events can be received through either [configuration callbacks]({{site.baseurl}}/rtc/configuration#rtc_client_connected) or [editor events]({{site.baseurl}}/rtc/events#rtcclientconnected).
## End-to-end encryption
The RTC plugin encrypts all content-specific traffic. This means the {{site.productname}} cloud services can not read any data transferred or know who is editing. Content and user data is only available to the page running {{ site.productname }}.
The RTC plugin encrypts all content-specific traffic. Clients are assigned a random presence ID when they connect, which is used to transmit cursor position along with their JWT user ID. This means the {{site.productname}} cloud services can not read any data transferred or know who is editing. Content and user data is only available to the page running {{ site.productname }}.
### RTC enabled features
@ -67,7 +69,14 @@ Here is a list of plugins currently supported:
### Browser support
RTC only supports the latest desktop versions of Chrome, Firefox, Microsoft Edge and Safari.
@ -6,6 +6,10 @@ description: Guide on how to setup JWT Authentication for RTC
keywords: jwt authentication
---
{% assign beta_feature = "The RTC plugin" %}
{% assign pre-release_type = "Open Beta" %}
{% include misc/beta-note.md %}
{% assign pluginname = "RTC" %}
{% assign plugincode = "rtc" %}
## Introduction
@ -28,18 +32,46 @@ Claims are additional data that can be sent as part of the JWT token. The RTC JW
| Data | Optional or required | Description |
|---|:---:|---|
| `sub` | required | The unique user ID (i.e. if `sub` is the same for two clients, you should trust them as if they're the same user). |
| `sub` | required | The unique user ID (for example, if `sub` is the same for two clients, you should trust them as if they're the same user). |
| `exp` | required | The timestamp when the token expires. |
The `sub` field is used to identify users to avoid sending sensitive or identity information to {{site.companyname}} in plain text. By minimizing the information in JWT claims and relying on the client-side resolution of user IDs, no private data will be transmitted through the RTC server without encryption.
{% include auth/jwt-endpoint-setup-procedure.md %}
## Generating a secure encryption key
> **Caution**: These suggestions may not guarantee a secure connection. If data secrecy is important to you please consult a security professional.
Encryption security is a trade off between the complexity of generating a key and the risk of compromise should the key be disclosed to an unknown third party. Here are some suggested ways to generate keys, in descending order of safety:
* Store a global list of keys for your application, and use the document ID along with random data to salt the current key _on your server_ to produce a key unique to the document session. Do not return the salt data to `keyHint`; return an identifier that can be used to look up the unique key on the server.
* Use a fixed random key for each document, and generate random salt data to provide a unique key for each session. Pass the salt data to `keyHint`.
* Generate and store a fixed random key for each document in your database. Ignore the `keyHint` input field and return a fixed arbitrary `keyHint` value.
## Encryption key rotation and key hints
The RTC configuration API is designed to support key rotation. Keys cannot be rotated on demand; if this is important to you, please contact {{site.companyname}} to discuss how we can best provide that functionality.
Document collaboration may be performed in multiple sessions. For example, when a new version of {{site.productname}} is deployed it may be incompatible with existing sessions. Only one session will be active at a time but older sessions may still be used to bootstrap new sessions. As such, old keys cannot be immediately discarded when a new key is requested.
In order to allow for key rotation, a key hint is supplied so the provider may tell the difference between these two cases and act accordingly. If the key hint is `null`, then the client wants the "current" key and can be issued a key different from any previously used key. If the key hint is set, then the client is requesting a previously-issued key so that it can read the session history.
A specific key hint may be specified in the key response. If it is not specified, then an empty string will be sent when the client requests that key in future.
> **Warning**: The key hint is transmitted _in plain text_. Do not store secret or sensitive information in the key hint.
The key hint can be a key thumbprint, ID, or other non-sensitive identifier that will help select the key, such as a timestamp. It is only recorded when `keyHint` is `null` in the request.
## Need help?
{{ site.companyname }} recommends looking into how JWT works; some knowledge about JWT is necessary to implement RTC. This can be tricky, so if you need some help contact our support.
{{ site.companyname }} recommends looking into how JWT works; some knowledge about JWT is necessary to implement RTC. This can be tricky, so if you need some help contact our support team.
## PHP token provider endpoint example
This example uses the [Firebase JWT library](https://github.com/firebase/php-jwt) provided through the Composer dependency manager. The private key should be a private key generated at {{site.accountpage}}.
This example uses the [Firebase JWT library](https://github.com/firebase/php-jwt) provided through the Composer dependency manager.
`$privateKey` should be a private key generated at {{site.accountpage}}.
### jwt.php
@ -81,7 +113,7 @@ try {
```js
tinymce.init({
selector: 'textarea',
selector: 'textarea', // change this value according to your HTML
plugins: 'rtc',
rtc_token_provider: () => {
return fetch('jwt.php', {
@ -95,6 +127,8 @@ tinymce.init({
This example shows how to set up a Node.js express handler that produces the tokens. It requires you to install the Express web framework and the `jsonwebtoken` Node module. For instructions on setting up a basic NodeJS Express server and adding {{site.productname}}, see: [Integrating TinyMCE into an Express JS App]({{site.baseurl}}/integrations/expressjs/).
`privateKey` should be a private key generated at {{site.accountpage}}.
### /jwt
```js
@ -138,7 +172,7 @@ app.listen(3000);
```js
tinymce.init({
selector: 'textarea',
selector: 'textarea', // change this value according to your HTML
plugins: 'rtc',
rtc_token_provider: () => {
return fetch('/jwt', {
@ -150,7 +184,7 @@ tinymce.init({
### More configuration
Once JWT authentication has been set up, the RTC plugin can be configured further using the options shown on the [RTC configuration options page]({{site.baseurl}}/rtc/configuration/). Don't forget to change the JWT Claim's (user id, user name) to get those from your system.
Once JWT authentication has been set up, the RTC plugin can be configured further using the options shown on the [RTC configuration options page]({{site.baseurl}}/rtc/configuration/). Don't forget to change the example JWT claims (user id, user name) to get those from your system.
If you want help [submit a support request]({{site.supporturl}}).
This documentation is in progress. Please contact us with any suggestions you think should be here.
## What happens if two clients attempt to establish a session at the same time? How does that impact on encryption key generation?
As noted in the [Document ID configuration option]({{site.baseurl}}/rtc/configuration/#rtc_document_id), if a document ID is not known to the server, new initial data will be uploaded. If two users manage to do this at the same time, one will be forced to wait for the other to establish the session. This has implications for configurations using a dynamic [`rtc_encryption_provider`]({{site.baseurl}}/rtc/configuration/#rtc_encryption_provider).
If the situation occurs, the connection process for the RTC plugin will be as follows:
1. Both plugins request a session for the document ID, and the server does not have an existing session.
1. Both plugins use the encryption provider to produce an encryption key with no key hint to encrypt the initial content.
1. Both plugins send a "create session" request to the server.
1. One connection "wins", and the other is "rejected".
1. The rejected connection waits for further instructions.
1. Once the winner completes establishing the session, the server broadcasts "new session available" to all clients connected for the same document ID. This message will include the key hint from the winning encryption key.
1. The rejected connection uses this key hint to generate the same encryption key as the winning connection and collaboration begins.
As a result of this process:
1. If encryption keys are dynamic, old keys cannot be discarded immediately when a new key is generated.
2. For any given key hint, the same encryption key must be returned every time.
Care must be taken to avoid losing encryption keys to this race condition.
### Server reports JWT authentication was issued in the future
JWT is a very strict protocol. If the computer that signs the JWT has a clock that is significantly ahead of the server clock, and tokens are signed on demand, this error can occur.
To resolve this issue ensure all computer clocks are synchronised using [NTP](https://en.wikipedia.org/wiki/Network_Time_Protocol) or a similar service.