guideCreating token endpoint

To connect CKEditor or Letters with CKEditor Cloud Services, you need to create your own token endpoint. This guide will explain a few principles that let you create it.

# How CKEditor Cloud Services uses tokens

To authenticate users, CKEditor Cloud Services uses tokens. The purpose of tokens is to inform the cloud services that the user has access to resources and to which environment the user should connect to. The authenticity of tokens is provided by a digital signature.

The token endpoint is where the client (using CKEditor/Letters) makes a request to get the token. It should return the token only if the user proves one’s identity.

# A few words about JSON Web Tokens

CKEditor Cloud Services tokens are represented as JSON Web Tokens (JWT). JWT is an open standard for transmitting digitally signed information. Using it ensures that the data come from a trusted source.

Tokens consist of three parts:

  • header
  • payload
  • signature

Each part is encoded in base64url and separated by periods:

header.payload.signature

An example token can look like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiSm9obiJ9.3MOd-0xmppWAX_86vMPjQ0PTKAniCtr762UTM5WuGa8

A header indicates that it is a JWT and defines the hashing algorithm for calculating the signature. It is simply a JSON object with the typ and alg properties, where typ always contains the JWT value and alg contains the name of the algorithm used.

# Payload

Payload is a JSON object with the claims. In CKEditor Cloud Services it is mostly used for specifying the environment, user data and permissions.

The JWT standard specifies some predefined claims like iss (issuer), exp (expiration time), sub (subject), aud (audience) or iat (issued at). In CKEditor Cloud Services we use iss for identifying environments and iat for checking if the token has not expired.

# Signature

Unlike the header and payload, the signature is not a JSON object but raw bytes produced by the cryptographic algorithm.

The signature is calculated for the header merged to the payload. It enables the authentication of data and verifying that the token has not been tampered with:

signature = HMACSHA256(
    base64UrlEncode(header) + "." + base64UrlEncode(payload),
    secret
)

CKEditor Cloud Services supports signatures created by HS256, HS384 and HS512 algorithms.

Remember not to disclose the secret key because it will allow to forge tokens.

# Requests to the token endpoint

The token for the CKEditor Cloud Services is requested by the CKEditor 5 rich-text editor’s features that needs the CKEditor Cloud Services to work, among others Easy Image and Collaborative editing.

# Simple usage

The easiest way to request the token endpoint is to configure the config.cloudServices.tokenUrl to the endpoint. It will be requested from the editor by the CloudServices plugin at the initialization stage and then after every hour by a simple HTTP request. If you need more control over the request see the next section.

ClassicEditor.create( element, {
    // ...
    cloudServices: {
        tokenUrl: 'https://example.com/cs-token-endpoint'
    }
} );

# Customizing the token request method

If you’d like to change the default method of requesting the token from the server (described in the previous section) then you can set the config.cloudServices.tokenUrl to a callback. This callback will be used by the CloudServices plugin plugin to retrieve the token. It allows specifying exact HTTP request params for the token endpoint that should return the token value. The callback needs to return a promise that might be resolved with the token value or rejected with an error.

Note that the editor will not be ready to use until the first token is obtained from the token endpoint. If an error occurs during the initial request, the editor will not start correctly.

An example below presents setting a custom callback to the tokenUrl configuration that adds custom header to the request:

const tokenUrl = 'https://example.com/cs-token-endpoint';

ClassicEditor.create( element, {
    // ...
    cloudServices: {
        tokenUrl: () => {
            return new Promise( ( resolve, reject ) => {
                const xhr = new XMLHttpRequest();

                xhr.open( 'GET', tokenUrl );

                xhr.addEventListener( 'load', () => {
                    const statusCode = xhr.status;
                    const xhrResponse = xhr.response;

                    if ( statusCode < 200 || statusCode > 299 ) {
                        return reject( new Error( 'Cannot download new token!' ) );
                    }

                    return resolve( xhrResponse );
                } );

                xhr.addEventListener( 'error', () => reject( new Error( 'Network Error' ) ) );
                xhr.addEventListener( 'abort', () => reject( new Error( 'Abort' ) ) );

                xhr.setRequestHeader( customHeader, customValue );

                xhr.send();
            } );
        }
    }
} );

The current token value is accessible via the CloudServices plugin.

const cloudServices = editor.plugins.get( 'CloudServices' );
const tokenValue = cloudServices.token.value;

# Responses from the token endpoint

The endpoint should respond with a generated token (as a string).

The example response might look like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiSm9obiJ9.3MOd-0xmppWAX_86vMPjQ0PTKAniCtr762UTM5WuGa8

# Token

The token for the cloud services should specify Environment ID as the iss property and it needs to be signed with Secret key as the secret key. Both can be found in the CKEditor Ecosystem customer dashboard. Optionally, the token can contain user data, otherwise CKEditor Cloud Services will treat the user as an anonymous user.

# User

If the user is not anonymous, the payload should contain the user object with an id property. You can also put there other data like name or email.

The user identifier should be unique within the environment scope, so you should not use the same environmentId in two applications because it can cause user ID collisions.

# Permissions

To define permissions to resources, the token payload should have the services object with specified service permissions.

{
    "services": {
        "service-name": {
            "permissions": {
                "resource-name": "access"
            }
        }
    }
}

# Permissions for Easy Image

There is no need to specify any permissions for Easy Image, because they are assigned to the environment. It means that each user that belongs to the environment is allowed to upload images. After upload, images are public and can be downloaded without any restrictions.

# Permissions for Collaboration

The user is authorized only to documents listed in permissions and may be limited to read or write scope. You can specify documents by providing their IDs or patterns if you want to specify a group of documents. A valid document ID should only contain letters, numbers and dashes.

# Patterns

If you want to allow the user to access a group of entities, you can provide a pattern with wildcard characters instead of the document ID. The pattern will cover multiple documents IDs.

Example: A user with access to docs-* has permissions to documents docs-titlepage and docs-category-document.

# Required payload properties

The following properties must be included in the payload:

  • iss - Environment ID.
  • iat - “Issued at”. Make sure that iat is present and contains correct time. Some JWT implementations do not include it by default. Also sometimes the system time may be invalid causing weird issues with tokens (see e.g. Docker for Mac time drift).
  • user - User information. Only id is required, but providing name and email is recommended.
  • services - ckeditor-collaboration permissions inside are required, if you plan to use collaboration. If you just use Easy Image, you may skip it.

# Example token payload

The example below presents a complete token payload with access to editing all documents in the environment:

{
    "iss": "NQoFK1NLVelFWOBQtQ8A",
    "iat": 1511963669,
    "user": {
        "id": "exampleuser",
        "email": "example@cksource.com",
        "name": "A User",
        "avatar": "http://example.com/avatars/john.png"
    },
    "services": {
        "ckeditor-collaboration": {
            "permissions": {
                "*": "write"
            }
        }
    }
}

# Tools

To create the token we highly recommend using libraries listed on jwt.io. You can also look at a sample token endpoint in Node.js or a token endpoint in PHP.

If you are having troubles with creating the token endpoint in another server side language, or if you have any other suggestions regarding the documentation, please contact us.