Spring Boot

This guide describes the integration of CKBox in a Spring Boot application. If you prefer to jump straight to the code, you can find the complete code of the application described in this guide on GitHub.

# Prerequisites

Before we start, please ensure that you have a recent version of JDK installed in your system. If the tool is not available from the command line, please follow the JDK installation instructions first.

# Creating the application

As a base in our example, we will create a new Spring Boot project using the Spring Initializr wizard. The examples in this document will use Maven. If you want to follow this guide step-by-step, set your project type to “Maven”. You can also use a different project type by replacing commands and configurations with equivalent ones in other tools.

Parameters used to create the project in this guide:

  • Group: io.ckbox
  • Artifact: spring-example
  • Name: spring-example
  • Project: Maven
  • Language: Java
  • Dependencies: Spring Web

After the project is created, start the development server:

./mvnw spring-boot:run

# Creating the authorization controller

First, let’s create a controller. The controller will be responsible for serving the token endpoint, used to authenticate users in CKBox.

CKBox, like other CKEditor Cloud Services, uses JWT tokens for authentication and authorization. All these tokens are generated on your application side and signed with a shared secret that you can obtain in the Customer Portal. For information on how to create access credentials, please refer to the Creating access credentials section in the Authentication guide.

Now that we have the required access credentials, namely: the environment ID and the access key, let’s create the token endpoint.

First, let’s add the com.auth0.jwt library for creating JWT tokens to pom.xml file:

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.4.0</version>
</dependency>

The dependency will be installed upon the next application start.

# Complete controller code

// src/main/java/io/ckbox/springexample/CKBoxController.java

package io.ckbox.springexample;

import java.time.Instant;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.*;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;

import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.JWT;

@RestController
public class CKBoxController {
    @Value("${environment_id}")
    private String environmentId;

    @Value("${access_key}")
    private String accessKey;

    @GetMapping("/ckbox/auth")
    public String tokenEndpoint() throws Exception {
        Map<String, Object> authClaim = new HashMap<>() {{
            put("ckbox", new HashMap<>() {{
                put("role", "user");
            }});
        }};

        Algorithm algorithm = Algorithm.HMAC256(this.accessKey.getBytes("ASCII"));

        String token = JWT.create()
            .withAudience(this.environmentId)
            .withIssuedAt(Instant.now())
            .withSubject("user id")
            .withClaim("auth", authClaim)
            .sign(algorithm);

        return token;
    }
}

As you can see on the code listing above, the access credentials required to sign JWT tokens are obtained from the environment variables. Now, you can run the application with:

environment_id=REPLACE-WITH-ENVIRONMENT-ID access_key=REPLACE-WITH-ACCESS-KEY ./mvnw spring-boot:run

# Adding CKBox script to the page

CKBox can be embedded in the application in multiple ways. We will use the CKBox script served from the CDN for simplicity.

Examples in this guide will cover three popular scenarios of using CKBox:

  • CKBox integrated with CKEditor 5.
  • CKBox used as a file picker in dialog mode.
  • CKBox used as a full-page application.

To avoid code repetition, let’s prepare a base Thymeleaf layout template that includes the CKBox script, which we will reuse in all three examples. First, add Thymeleaf dependencies to the pom.xml file:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<dependency>
    <groupId>nz.net.ultraq.thymeleaf</groupId>
    <artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>

Then you can create a layout file:

<!-- src/main/resources/templates/layout.html -->
<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<html>
<head>
    <meta charset="UTF-8"/>
    <title>CKBox example</title>
    <script src="https://cdn.ckbox.io/ckbox/2.1.0/ckbox.js"></script>
</head>
<body>
    <section layout:fragment="content">
        <p>Page content goes here</p>
    </section>
</body>
</html>

Finally, prepare an MVP controller for the examples:

// src/main/java/io/ckbox/springexample/CKBoxExampleWebpageController.java
package io.ckbox.springexample;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;

@Controller
public class CKBoxExampleWebpageController {
}

# CKBox with CKEditor 5

In this example, we will use the quickest and easiest way to run CKEditor 5 – served from the CDN. For more advanced integration scenarios, please refer to the CKEditor 5 documentation.

Let’s create the child view that extends the layout we have created in the previous step:

<!-- src/main/resources/templates/ckbox-example-ckeditor.html -->
<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
    layout:decorate="~{layout}">
<head>
    <script type="importmap">
        {
            "imports": {
                "ckeditor5": "https://cdn.ckeditor.com/ckeditor5/43.1.0/ckeditor5.js",
                "ckeditor5/": "https://cdn.ckeditor.com/ckeditor5/43.1.0/"
            }
        }
    </script>
    <link rel="stylesheet" href="https://cdn.ckeditor.com/ckeditor5/43.1.0/ckeditor5.css" />
</head>
<body>
    <section layout:fragment="content">
        <textarea id="editor"><h1>Example</h1><p>Hello world</p></textarea>

        <script type="module">
            import {
                ClassicEditor,
                CKBox,
                Essentials,
                Bold,
                Italic,
                Font,
                Paragraph
            } from 'ckeditor5';

            ClassicEditor
                .create( document.querySelector( '#editor' ), {
                    plugins: [ CKBox, Essentials, Bold, Italic, Font, Paragraph ],
                    ckbox: {
                        tokenUrl: 'https://your.token.url',
                        theme: 'lark',
            allowExternalImagesEditing: [ /^data:/, 'origin', /ckbox/ ]
                    },
                    toolbar: [
                        'ckbox', '|', 'undo', 'redo', '|', 'bold', 'italic', '|',
                        'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor'
                    ],
                } )
                .catch( error => {
                    console.error( error );
                } );
        </script>
    </section>
</body>
</html>

and add the view to the controller:

@Controller
public class CKBoxExampleWebpageController {
    @GetMapping("/ckbox-example-ckeditor")
    public String ckeditorExample(Model model) {
        return "ckbox-example-ckeditor";
    }
}

The above example includes a predefined CKEditor 5 build from the CDN, which includes the required CKBox plugin. Then, CKEditor 5 is configured to use CKBox by setting the required parameters of the ckbox attribute. Please note that in the ckbox.tokenUrl configuration option we pass the URL of the token endpoint created in one of the previous steps. It will be used to obtain JWT tokens used to authenticate users in CKBox.

# CKBox as file picker

One of the common scenarios is to use CKBox as a file picker, where the user can choose one of the files stored in the file manager. After choosing the file, we want to obtain information about the chosen files, especially their URLs. This can be achieved using the assets.onChoose callback passed as the CKBox’s configuration option:

<!-- src/main/resources/templates/ckbox-example-modal.html -->
<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
    layout:decorate="~{layout}">
<body>
    <section layout:fragment="content">
        <input type="text" id="file-url"></input><button id="choose">Choose file</button>
        <div id="ckbox"></div>
        <script>
            document.getElementById('choose').addEventListener('click', function () {
                CKBox.mount(document.querySelector('#ckbox'), {
                    tokenUrl: `${ location.origin }/ckbox/auth`,
                    dialog: true,
                    assets: {
                        // Callback executed after choosing assets
                        onChoose: (assets) => {
                            document.getElementById('file-url').value = assets[0].data.url;

                            assets.forEach((asset) => {
                                console.log(asset.data.url);
                            })
                        }
                    }
                });
            });
        </script>
    </section>
</body>
</html>

@Controller
public class CKBoxExampleWebpageController {
    // ...

    @GetMapping("/ckbox-example-modal")
    public String modalExample(Model model) {
        return "ckbox-example-modal";
    }
}

# CKBox in full-page mode

To start CKBox in full-page mode, you can attach it to the document.body and adjust the required CSS styles:

<!-- src/main/resources/templates/ckbox-example-full-page.html -->
<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
    layout:decorate="~{layout}">
    <style>
        html, body {
            margin: 0;
            padding: 0;
            height: 100vh;
        }
    </style>
<body>
    <section layout:fragment="content">
        <script>
            CKBox.mount(document.body, {
                tokenUrl: `${ location.origin }/ckbox/auth`
            });
        </script>
    </section>
</body>
</html>
@Controller
public class CKBoxExampleWebpageController {
    // ...

    @GetMapping("/ckbox-example-full-page")
    public String fullPageExample(Model model) {
        return "ckbox-example-full-page";
    }
}

# Complete code

You can find the complete code of the application described in this guide on GitHub.