We're hiring
Products

Channels

Beams

Chatkit

Textsync

preview
DocsTutorialsSupportCareersPusher Blog
Sign InSign Up
Products

Channels

Build scalable, realtime features into your apps

Features Pricing

Beams

Send push notifications programmatically at scale

Pricing

Chatkit

Build chat into your app in hours, not days

Pricing
Developers

Docs

Read the docs to learn how to use our products

Channels Beams Chatkit

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Status

Check on the status of any of our products

Products

Channels

Build scalable, realtime features into your apps

Features Pricing

Beams

Send push notifications programmatically at scale

Pricing

Chatkit

Build chat into your app in hours, not days

Pricing

Textsync

preview

Collaborative, realtime editing made easy

Developers

Docs

Read the docs to learn how to use our products

Channels Beams Chatkit

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Status

Check on the status of any of our products

Sign InSign Up

Building a Java authentication server for Chatkit

  • Esteban Herrera
November 1st, 2018
You will need JDK 8+ and a Java IDE installed on your machine. You should have some familiarity with Java development and Spring Boot.

In this tutorial, you’ll learn how Chatkit’s authentication process works and how to create a custom authentication server in Java.

Chatkit is a service that comes with a lot of features to add chat functionality to your web and mobile applications, without sacrificing the flexibility to integrate it with your existing infrastructure.

One example of that flexibility is the way Chatkit allows you to define your own authentication flows.

Here’s a sample chat app that shows the process from the users perspective:

For this tutorial, we are going to assume that the user information is stored in a directory server (like Active Directory) that can be accessed through LDAP.

This way, the server checks if the user exists in the directory server and if that is the case, it generates a JSON Web Token (JWT) in the format expected by Chatkit so the user can access the chat.

If the user doesn’t exist or if she doesn’t provide a correct password, an error is sent from the server:

The focus will be on how to build the authentication server, so this tutorial won’t cover how to build the React application for the chat.

We’ll clone an adapted version of the chat app built by Alex Booker in this video tutorial to test the server. If you want to build your own chat, you can follow that tutorial or others. Like this one to build an HTML Chatbox. Or this one that integrate a chat into a chess game.

For reference, here you can find the entire source code for the server.

Prerequisites

Here’s what you need to have installed/configured to follow this tutorial:

I also assume that you are familiar with:

  • Java development (an intermediate level at least)
  • Spring Boot

Let’s start by creating a Chatkit application.

Creating a Chatkit application

Create a free account for Chatkit here and log in.

In your dashboard, create a new Chatkit instance and then, take note of your Instance Locator identifier and Secret Key from the Credentials section, we’re going to need them later.

To keep things simple, I’m going to create two users and a room for the chat using the dashboard instead of using the API or a Server SDK to create that functionality into the application.

In the Instance Inspector section of your instance, create two users. For this example, I’ll use the identifiers eh and eh2. Of course, you can use any identifiers you want, and optionally, you can also give them a display name:

Next, in the User Actions menu, click on the Create and join a room link to create a room. Give it a name and take note of the Room ID because we are going to need it too:

You don’t need to add the other user to the room. Chatkit will added automatically the first time she uses the chat.

And that’s all the configuration we need for Chatkit.

Now let’s set up the project for the server.

Setting up the server application

Go to http://start.spring.io and create a project with the following information:

  • Project type: Maven Project
  • Language: Java
  • Spring Boot version: 2.0.6 (or superior)
  • Group: com.example
  • Artifact: authserver
  • Dependencies: Web and LDAP

You can choose any group and artifact identifiers you want, but in this tutorial, I’ll use the ones specified above to refer to the files of the project.

Click on the Generate Project button, download the ZIP file, extract it and open the project in an IDE. I recommend IntelliJ IDEA Community Edition.

Next, open the pom.xml file at the root of the project and in the dependencies section, add the following two dependencies:

    <dependencies>
       ...

       <dependency>
          <groupId>com.unboundid</groupId>
          <artifactId>unboundid-ldapsdk</artifactId>
       </dependency>

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

And import the changes to the Maven project so these dependencies can be downloaded and added to the project.

We’ll use unboundid-ldapsdk to configure an embedded LDAP server and java-jwt to generate the JSON web token for Chatkit.

Configuring the LDAP server

We are going to work with an embedded LDAP server to make the project easy to run and test.

Open the file src/main/resources/application.properties and add the following properties:

    spring.ldap.embedded.ldif=classpath:schema.ldif
    spring.ldap.embedded.port=8389
    spring.ldap.embedded.base-dn=dc=mycompany,dc=com

    spring.ldap.embedded.credential.username=uid=admin
    spring.ldap.embedded.credential.password=12345

    spring.ldap.embedded.validation.enabled=false

This will tell Spring Boot to start an embedded LDAP server on port 8389 with the base DN dc=mycompany,dc=com (the point from where the server will search for users) and the content of the file schema.ldif. And create a user for the server with the credentials admin/12345.

Now create the file src/main/resources/schema.ldif and add the following content to create some organizational units:

    dn: dc=mycompany,dc=com
    objectclass: top
    objectclass: domain
    objectclass: extensibleObject
    dc: mycompany

    # Organizational Units
    dn: ou=groups,dc=mycompany,dc=com
    objectclass: top
    objectclass: organizationalUnit
    ou: groups

    dn: ou=people,dc=mycompany,dc=com
    objectclass: top
    objectclass: organizationalUnit
    ou: people

Next, to add the users, I’ll be using the sn property as the username, so make sure this value corresponds to the ID of the user you registered on the Chatkit dashboard (eh and eh2 in my case):

    ...

    # Create Users
    dn: uid=u1,ou=people,dc=mycompany,dc=com
    objectclass: top
    objectclass: person
    objectclass: organizationalPerson
    objectclass: inetOrgPerson
    cn: User One
    sn: eh
    uid: u1
    password: 12345

    dn: uid=u2,ou=people,dc=mycompany,dc=com
    objectclass: top
    objectclass: person
    objectclass: organizationalPerson
    objectclass: inetOrgPerson
    cn: User Two
    sn: eh2
    uid: u2
    password: 12345

Finally, we can also create a developer group for these users:

    # Create Groups
    dn: cn=developers,ou=groups,dc=mycompany,dc=com
    objectclass: top
    objectclass: groupOfUniqueNames
    cn: developers
    ou: developer
    uniqueMember: uid=u1,ou=people,dc=mycompany,dc=com
    uniqueMember: uid=u2,ou=people,dc=mycompany,dc=com

At this point, you can test this embedded server by running the main class of the application (com.example.authserver.AuthserverApplication.java in my case) and using an LDAP browser (like JXplorer), connect to the server using the information defined in the application.properties file:

To check if the users were registered:

Now let’s see how to query these users.

Searching for users

Using Spring Data support for LDAP repositories, we only need to create a class to hold the user’s information (com.example.authserver.User):

    import org.springframework.ldap.odm.annotations.Attribute;
    import org.springframework.ldap.odm.annotations.Entry;
    import org.springframework.ldap.odm.annotations.Id;

    import javax.naming.Name;

    @Entry(
        base = "ou=people,dc=mycompany,dc=com",
        objectClasses = { "person", "inetOrgPerson", "top", "organizationalPerson" }
    )
    final public class User {
        @Id
        private Name id;

        @Attribute(name = "sn")
        private String username;

        @Attribute(name = "password")
        private String password;

        @Attribute(name = "cn")
        private String fullName;

        public Name getId() {
            return id;
        }

        public void setId(Name id) {
            this.id = id;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String getFullName() {
            return fullName;
        }

        public void setFullName(String fullName) {
            this.fullName = fullName;
        }

        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    ", fullName='" + fullName + '\'' +
                    '}';
        }
    }

And define the operation we are going to use (searching by username and password) in an interface that Spring Data will implement automatically for us (com.example.authserver.UserRepository):

    import org.springframework.data.ldap.repository.LdapRepository;
    import org.springframework.stereotype.Repository;

    @Repository
    public interface UserRepository extends LdapRepository<User> {
        User findByUsernameAndPassword(String username, String password);
    }

Now let’s create the authentication endpoint to determine if the user exists and generate the JSON web token.

Creating the authentication endpoint

The authentication flow for Chatkit is defined as follows:

  • The client defines a token provider from which a JWT is requested.
  • The client requests a JWT from this provider.
  • The token provider server receives the client's request, validates it, and returns a valid JWT.
  • The client is now ready to make requests to the Chatkit service with the JWT.

There are Server SDKs for Node.js, Go and other languages, that provide helper methods for this process.

However, it’s also possible to generate tokens without the help of the server SDKs using a JWT library and language of your choice.

When the client requests a token, Chatkit calls the authentication endpoint sending a query string with the user ID (user_id) of the client and other custom parameters (the password of the user in this example): http://localhost:8080/authenticate?user_id=eh&passw=12345

So let’s create a class for the endpoint, com.example.authserver.AuthenticationController, to define a POST mapping that takes those query parameters and use the repository created in the previous section to find the user:

    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.*;

    @RestController
    public class AuthenticationController {
        private UserRepository userRepository;

        public AuthenticationController(UserRepository userRepository) {
            this.userRepository = userRepository;
        }

        @PostMapping("/authenticate")
        public ResponseEntity authenticate(@RequestParam(value="user_id") String userId,
                                           @RequestParam(value="passw") String passw) {
            Object body = null;
            HttpStatus status = null;

            User user = userRepository.findByUsernameAndPassword(userId, passw);

            if(user != null) {
                // Generate and send a valid JWT
            } else {
                status = HttpStatus.BAD_REQUEST;
                body = "User not found";
            }

            return ResponseEntity
                    .status(status)
                    .body(body);
        }
    }

If the user is not found, a BAD_REQUEST status (400) and an error message will be sent to the client.

Otherwise, we’ll generate and send a valid token to the client.

Chatkit only accepts tokens signed with the algorithm HMAC with SHA-256 and the following payload:

  • instance: Your Chatkit instance ID
  • iss: The identifier for the key used to sign the token with the format api_keys/<key ID>
  • iat: The Unix timestamp when the token was issued (seconds)
  • exp: The Unix timestamp when the token expires (it should be later than iat)
  • sub: The user ID
  • su: true: Optional sudo token claim required for the user creation and deletion operations

Remember the values from the Credential section of your Chatkit instance dashboard?

They have a format similar to the following:

  • Instance Locator: “v1:us1:
  • Secret Key: “:

Notice that the last part of the Instance Locator is the value of the instance ID.

And the Secret Key is composed of two parts, the ID of the key, and the secret key that you’ll use to sign the payload of the token.

For example, if your credentials are:

  • Instance Locator: v1:us1:3635013a-2594-4640
  • Secret Key: 2b06ce7d-5c60-41ad:fRvtU+JoxdSlUqWezh2jojiiQcH+lFs032MzeIcAw8g=

A sample payload signed with the key :fRvtU+JoxdSlUqWezh2jojiiQcH+ will be something like this:

    {
      "instance": "3635013a-2594-4640",
      "iss": "api_keys/2b06ce7d-5c60-41ad",
      "iat": 1508839109,
      "exp": 1508842109,
      "sub": "eh"
    }

Finally, once you have generated a token from the payload, you have to send to the client a JSON object with the following keys:

  • access_token (string): the token.
  • user_id (string): user_id included as the token subject.
  • expires_in (int): expiry time of the token.

So to implement all this, first, add the variables to hold the instance locator and secret key parts:

    public class AuthenticationController {

        private String key = "<YOUR_WHOLE_SECRET_KEY>"; // something like 2b06ce7d-5c60-41ad:fRvtU+JoxdSlUqWezh2jojiiQcH+lFs032MzeIcAw8g= 
        private String chatkitInstanceID = "<YOUR_INSTANCE_ID>"; // something like 3635013a-2594-4640

        private String keyId;
        private String keySecret;

        ...
    }

And add to the constructor the code to get the secret key parts:

    public class AuthenticationController {

        ...

        public AuthenticationController(UserRepository userRepository) {
            this.userRepository = userRepository;

            String[] keyParts = key.split(":");
            this.keyId = keyParts[0];
            this.keySecret = keyParts[1];
        }

        ...
    }

To generate the token, we’ll add a method that uses the java-jwt library in the following way:

    public class AuthenticationController {
        ...

        private String generateToken(String userId, Instant time, Instant expireTime) {
            Algorithm algorithm = Algorithm.HMAC256(this.keySecret);
            String jws = JWT.create()
                    .withClaim("instance", this.chatkitInstanceID)
                    .withIssuer("api_keys/" + this.keyId)
                    .withIssuedAt(Date.from(time))
                    .withExpiresAt(Date.from(expireTime))
                    .withSubject(userId)
                    .sign(algorithm);
            System.out.println(jws);

            return jws;
        }
    }

And this will send the response in the expected format if the user was found (specifying an expiration time of 3600 seconds):

    public class AuthenticationController {
        ...

        @PostMapping("/authenticate")
        public ResponseEntity authenticate(@RequestParam(value="user_id") String userId,
                                           @RequestParam(value="passw") String passw) {
            ...

            if(user != null) {
                Map<String, String> map = new HashMap<>();
                Instant now = Instant.now();
                Instant expireTime = now.plusSeconds(3600);
                String token = generateToken(userId, now, expireTime);

                map.put("access_token", token);
                map.put("expires_in", Long.toString(expireTime.toEpochMilli()));
                map.put("user_id", userId);

                status = HttpStatus.OK;
                body = map;
            } else {
                ...
            }

            ...
        }

        ...
    }

Finally, since this endpoint will be called from another application (in another server), we need to specify the CORS headers to allow this.

In the main class (com.example.authserver.AuthserverApplication.java in my case), add the following import statements and method to configure CORS globally for any method:

    ...
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

    @SpringBootApplication
    public class Authdemo1Application {

       ...

       @Bean
       public WebMvcConfigurer corsConfigurer() {
          return new WebMvcConfigurer() {
             @Override
             public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**");
             }
          };
       }
    }

And that’s all for the server.

Now let’s test it.

Testing the authentication server

Run the main class of the project (com.example.authserver.AuthserverApplication.java in my case) to start the server.

You can use a REST client (like Postman) to test the authentication endpoint using the following information: URL: http://localhost:8080/authenticate?user_id=eh&passw=12345 (specify your own user credentials) HTPP method: POST

But as mentioned before, we’re going to use a sample chat application made with React to test the server.

So clone the repository at https://github.com/eh3rrera/chatkit-custom-auth-test-chat and at the top of the file src/App.js enter:

  • Your Instance Locator ID (for example, v1:us1:3635013a-2594-4640)
  • The URL of the authorization endpoint of your server (for example, http://localhost:8080/authenticate).

In src/ChatScreen.js, enter the ID of the chat room you created in your Chatkit instance dashboard.

Next, in a terminal window, cd into the app directory and execute npm install to install the project dependencies and npm start to run the app.

Enter the user credentials (by default, eh/12345) and play with the application:

Conclusion

You have learned how to create a custom authorization server for Chatkit in Java.

Chatkit has a test token provider, however, its use is not recommended for production.

For some languages, Chatkit provides server SDKs that abstract most of this functionality, but if there is no SDK for the language or technology you are targeting, you can use the concepts of this tutorial to build a solution.

You can know more about the authentication process in this page from Chatkit’s documentation.

But from here, you can extend the application in many ways, for example:

  • Add a sudo claim to the token for certain users (remember that this claim is required if the user wants to perform create and delete operations.
  • Implement more functionality on the server-side, for example, user creation (check out the Chatkit API reference).
  • Build your own chat app to test the server

Remember that all of the source code for this application is available on GitHub.

Clone the project repository
  • Chat
  • Java
  • Chatkit

Products

  • Channels
  • Beams
  • Chatkit
  • Feeds
  • Textsync

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.