Authentication is the process of validating identities. Agora uses digital tokens to authenticate users and their privileges before they access an Agora service, such as joining an Agora call, or logging in to Chat.
To securely connect to Chat, you use the following token types:
- User token: User level access to Chat from your app using Chat SDK. User tokens are used to authenticate users when they log in to your Chat application.
- App token: Admin level access to Chat using the REST API. App tokens grant admin privileges for the app developer to manage the Chat applications, for example, creating and deleting users. For details, see Implement an Agora app token server for Chat.
This page shows you how to create a Chat user token server and a Chat client app. The client app retrieves a user token from the token server. This token enables the current user to access Chat.
The following figure shows the steps in the authentication flow:
A user token is a dynamic key generated on your app server that is valid for a maximum of 24 hours. When your users login from your app, Chat reads the information stored in the token and uses it to authenticate the user. A user token contains the following information:
- The App ID of your Agora project.
- The App Certificate of your Agora project.
- The UUID of the user to be authenticated. The user UUID is a unique internal identification that Chat generates for a user through User Registration REST APIs.
- The valid duration of the token.
Ensure that you meet the following requirements before proceeding:
This section shows you how to supply and consume a token used to authenticate a user with Chat. This token also controls the functionality each user has access to in Chat. The encryption source code used to generate this token is provided by Agora.
Token generators create the tokens requested by your client app to enable secure access to Chat. To serve these tokens you deploy a generator in your security infrastructure.
To show the authentication workflow, this section shows how to build and run a token server written in Java on your local machine.
The following figure shows the API call sequence of generating a Chat user token:
This sample server is for demonstration purposes only. Do not use it in a production environment.
-
Create a Maven project in IntelliJ, set the name of your project, choose the location to save your project, then click Finish.
-
In <Project name>/pom.xml
, add the following dependencies and then reload the Maven project:
_51 <java.version>1.8</java.version>
_51 <spring-boot.version>2.4.3</spring-boot.version>
_51<packaging>jar</packaging>
_51<dependencyManagement>
_51 <groupId>org.springframework.boot</groupId>
_51 <artifactId>spring-boot-dependencies</artifactId>
_51 <version>${spring-boot.version}</version>
_51 <scope>import</scope>
_51</dependencyManagement>
_51 <groupId>org.springframework.boot</groupId>
_51 <artifactId>spring-boot-starter-web</artifactId>
_51 <groupId>org.springframework.boot</groupId>
_51 <artifactId>spring-boot-starter</artifactId>
_51 <groupId>org.springframework.boot</groupId>
_51 <artifactId>spring-boot-configuration-processor</artifactId>
_51 <groupId>commons-codec</groupId>
_51 <artifactId>commons-codec</artifactId>
_51 <version>1.14</version>
_51 <groupId>org.springframework.boot</groupId>
_51 <artifactId>spring-boot-maven-plugin</artifactId>
_51 <version>2.4.1</version>
_51 <goal>repackage</goal>
-
Import the token builders provided by Agora to this project.
-
Download the chat and media packages.
-
In the token server project, create a com.agora.chat.token.io.agora
package under <Project name>/src/main/java
.
-
Copy the chat
and media
packages and paste under com.agora.chat.token.io.agora
. The following figure shows the project structure:
-
Fix the import errors in chat/ChatTokenBuilder2
and media/AccessToken
.
- In
ChatTokenBuilder2
, the import should be import com.agora.chat.token.io.agora.media.AccessToken2
.
- In
AccessToken
, the import should be import static com.agora.chat.token.io.agora.media.Utils.crc32
.
-
In <Project name>/src/main/resource
, create a application.properties
file to hold the information for generating tokens and update it with your project information.
Note that the value for appid
, appcert
, and appkey
should be a string without quotation marks.
_12## Fill the App ID of your Agora project
_12## Fill the app certificate of your Agora project
_12## Fill the app key of the Chat service
_12## REST API domain for the Chat service
_12## Set the valid duration (in seconds) for the token
For details on how to get the app key and REST API domain, see Enable and Configure Chat.
-
In the com.agora.chat.token
package, create a Java class named AgoraChatTokenController
, with the following content:
_120package com.agora.chat.token;
_120import com.agora.chat.token.io.agora.chat.ChatTokenBuilder2;
_120import org.springframework.beans.factory.annotation.Value;
_120import org.springframework.util.StringUtils;
_120import org.springframework.web.bind.annotation.CrossOrigin;
_120import org.springframework.web.bind.annotation.GetMapping;
_120import org.springframework.web.bind.annotation.PathVariable;
_120import org.springframework.web.bind.annotation.RestController;
_120import org.springframework.web.client.RestTemplate;
_120import org.springframework.http.*;
_120import org.springframework.web.client.RestClientException;
_120public class AgoraChatTokenController {
_120 private String appid;
_120 @Value("${appcert}")
_120 private String appcert;
_120 @Value("${expire.second}")
_120 private String appkey;
_120 private String domain;
_120 private final RestTemplate restTemplate = new RestTemplate();
_120 // Get the Chat user token
_120 @GetMapping("/chat/user/{chatUserName}/token")
_120 public String getChatToken(@PathVariable String chatUserName) {
_120 if (!StringUtils.hasText(appid) || !StringUtils.hasText(appcert)) {
_120 return "appid or appcert is not empty";
_120 if (!StringUtils.hasText(appkey) || !StringUtils.hasText(domain)) {
_120 return "appkey or domain is not empty";
_120 if (!appkey.contains("#")) {
_120 return "appkey is illegal";
_120 if (!StringUtils.hasText(chatUserName)) {
_120 return "chatUserName is not empty";
_120 ChatTokenBuilder2 builder = new ChatTokenBuilder2();
_120 String chatUserUuid = getChatUserUuid(chatUserName);
_120 if (chatUserUuid == null) {
_120 chatUserUuid = registerChatUser(chatUserName);
_120 return builder.buildUserToken(appid, appcert, chatUserUuid, expire);
_120 // Get the UUID for a username
_120 private String getChatUserUuid(String chatUserName) {
_120 String orgName = appkey.split("#")[0];
_120 String appName = appkey.split("#")[1];
_120 String url = "http://" + domain + "/" + orgName + "/" + appName + "/users/" + chatUserName;
_120 HttpHeaders headers = new HttpHeaders();
_120 headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
_120 headers.setBearerAuth(exchangeToken());
_120 HttpEntity<Map<String, String>> entity = new HttpEntity<>(null, headers);
_120 ResponseEntity<Map> responseEntity = null;
_120 responseEntity = restTemplate.exchange(url, HttpMethod.GET, entity, Map.class);
_120 } catch (Exception e) {
_120 System.out.println("get chat user error : " + e.getMessage());
_120 if (responseEntity != null) {
_120 List<Map<String, Object>> results = (List<Map<String, Object>>) responseEntity.getBody().get("entities");
_120 return (String) results.get(0).get("uuid");
_120 // Create a user with the password "123" and get the UUID
_120 private String registerChatUser(String chatUserName) {
_120 String orgName = appkey.split("#")[0];
_120 String appName = appkey.split("#")[1];
_120 String url = "http://" + domain + "/" + orgName + "/" + appName + "/users";
_120 HttpHeaders headers = new HttpHeaders();
_120 headers.setContentType(MediaType.APPLICATION_JSON);
_120 headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
_120 headers.setBearerAuth(exchangeToken());
_120 Map<String, String> body = new HashMap<>();
_120 body.put("username", chatUserName);
_120 body.put("password", "123");
_120 HttpEntity<Map<String, String>> entity = new HttpEntity<>(body, headers);
_120 ResponseEntity<Map> response;
_120 response = restTemplate.exchange(url, HttpMethod.POST, entity, Map.class);
_120 } catch (Exception e) {
_120 throw new RestClientException("register chat user error : " + e.getMessage());
_120 List<Map<String, Object>> results = (List<Map<String, Object>>) response.getBody().get("entities");
_120 return (String) results.get(0).get("uuid");
_120 // Get the Agora app token
_120 private String getAppToken() {
_120 if (!StringUtils.hasText(appid) || !StringUtils.hasText(appcert)) {
_120 return "appid or appcert is not empty";
_120 ChatTokenBuilder2 builder = new ChatTokenBuilder2();
_120 return builder.buildAppToken(appid, appcert, expire);
_120 // Convert the Agora app token to Chat app token
_120 private String exchangeToken() {
_120 String orgName = appkey.split("#")[0];
_120 String appName = appkey.split("#")[1];
_120 String url = "http://" + domain + "/" + orgName + "/" + appName + "/token";
_120 HttpHeaders headers = new HttpHeaders();
_120 headers.setContentType(MediaType.APPLICATION_JSON);
_120 headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
_120 headers.setBearerAuth(getAppToken());
_120 Map<String, String> body = new HashMap<>();
_120 body.put("grant_type", "agora");
_120 HttpEntity<Map<String, String>> entity = new HttpEntity<>(body, headers);
_120 ResponseEntity<Map> response;
_120 response = restTemplate.exchange(url, HttpMethod.POST, entity, Map.class);
_120 } catch (Exception e) {
_120 throw new RestClientException("exchange token error : " + e.getMessage());
_120 return (String) Objects.requireNonNull(response.getBody()).get("access_token");
-
In the com.agora.chat.token
package, create a Java class named AgoraChatTokenStarter
, with the following content:
_9package com.agora.chat.token;
_9import org.springframework.boot.SpringApplication;
_9import org.springframework.boot.autoconfigure.SpringBootApplication;
_9@SpringBootApplication(scanBasePackages = "com.agora")
_9public class AgoraChatTokenStarter {
_9 public static void main(String[] args) {
_9 SpringApplication.run(AgoraChatTokenStarter.class, args);
-
To start the server, click the green triangle button, and select Debug "AgoraChatTokenStarter...".
This section uses the Web client as an example to show how to use a token for client-side user authentication.
To show the authentication workflow, this section shows how to build and run a Web client on your local machine.
This sample client is for demonstration purposes only. Do not use it in a production environment.
To implement the Web client, do the following:
-
Create a project structure for a Chat Web app. In the project root folder, create the following files:
index.html
: The user interface.
index.js
: The app logic.
webpack.config.js
: The webpack configuration.
-
To configure webpack, copy the following code into webpack.config.js
:
_15const path = require('path');
_15 filename: 'bundle.js',
_15 path: path.resolve(__dirname, './dist'),
-
Set up the npm package for your Web app.
In terminal, navigate to the project root directory and run npm init
. This creates a package.json
file.
-
Configure the dependencies and scripts for your project.
In package.json
, add the following code:
_21 "build": "webpack --config webpack.config.js",
_21 "start:dev": "webpack serve --open --config webpack.config.js"
_21 "agora-chat-sdk": "latest"
_21 "webpack": "^5.50.0",
_21 "webpack-cli": "^4.8.0",
_21 "webpack-dev-server": "^3.11.2"
-
Create the UI for your app.
In index.html
, add the following code:
_19 <title>Chat Token demo</title>
_19 <div class="input-field">
_19 <label>Username</label>
_19 <input type="text" placeholder="Username" id="username" />
_19 <button type="button" id="login">Login</button>
_19 <script src="./dist/bundle.js"></script>
-
Create the app logic.
In index.js
, add the following code and replace <Your App Key>
with your app key.
In the code example, you can see that token is related to the following code logic in the client:
- Call
open
to log in to the Chat system with token and username. You must use the username that is used to register the user and get the UUID.
- Fetch a new token from the app server and call
renewToken
to update the token of the SDK when the token is about to expire and when the token expires. Agora recommends that you regularly (such as every hour) generate a token from the app server and call renewToken
to update the token of the SDK to ensure that the token is always valid.
_65import WebIM from "agora-chat-sdk";
_65WebIM.conn = new WebIM.connection({
_65 appKey: "<Your App Key>",
_65document.getElementById("login").onclick = function () {
_65 username = document.getElementById("username").value.toString();
_65 // Fetch the Chat user token with username.
_65 fetch(`http://localhost:8090/chat/user/${username}/token`)
_65 .then((res) => res.text())
_65 // Login to Chat with username and token
_65// Add an event handler
_65WebIM.conn.addEventHandler("AUTHHANDLER", {
_65 // Connected to the server
_65 .getElementById("log")
_65 .appendChild(document.createElement("div"))
_65 .append("Connect success !");
_65 // Receive a text message
_65 onTextMessage: (message) => {
_65 console.log(message);
_65 .getElementById("log")
_65 .appendChild(document.createElement("div"))
_65 .append("Message from: " + message.from + " Message: " + message.data);
_65 // Renew the token when the token is about to expire
_65 onTokenWillExpire: (params) => {
_65 .getElementById("log")
_65 .appendChild(document.createElement("div"))
_65 .append("Token is about to expire");
_65 refreshToken(username);
_65 // Renew the token when the token has expired
_65 onTokenExpired: (params) => {
_65 .getElementById("log")
_65 .appendChild(document.createElement("div"))
_65 .append("The token has expired");
_65 refreshToken(username);
_65 onError: (error) => {
_65 console.log("on error", error);
_65function refreshToken(username) {
_65 fetch(`http://localhost:8090/chat/user/${username}/token`)
_65 .then((res) => res.text())
_65 WebIM.conn.renewToken(token);
-
To build and run your project, do the following:
-
To install the dependencies, run npm install
.
-
To build and run the project using webpack, run the following commands:
_5# Use webpack to package the project
_5# Use webpack-dev-server to run the project
The index.html
page opens in your browser.
-
Input a username and click the login button.
Open the browser console, and you can see the web client performs the following actions:
- Generates a user token.
- Connects to the Chat system.
- Renews a token when it is about to expire.
This section introduces token generator libraries, version requirements, and related documents about tokens.
Agora provides an open-source AgoraDynamicKey repository on GitHub, which enables you to generate tokens on your server with programming languages such as C++, Java, and Go.
This section introduces the method to generate a user token. Take Java as an example:
_12public String buildUserToken(String appId, String appCertificate, String uuid, int expire) {
_12 AccessToken2 accessToken = new AccessToken2(appId, appCertificate, expire);
_12 AccessToken2.Service serviceChat = new AccessToken2.ServiceChat(uuid);
_12 serviceChat.addPrivilegeChat(AccessToken2.PrivilegeChat.PRIVILEGE_CHAT_USER, expire);
_12 accessToken.addService(serviceChat);
_12 return accessToken.build();
_12 } catch (Exception e) {
Parameter | Description |
---|
appId | The App ID of your Agora project. |
appCertificate | The App Certificate of your Agora project. |
uuid | The unique identifier (UUID) of a user in the Chat system. |
expire | The valid duration (in seconds) of the token. The maximum is 86,400, that is, 24 hours. |
A user token is valid for a maximum of 24 hours.
When the Chat SDK is in the isConnected(true)
state, the user remains online even if the user token expires. If a user logs in with an expired token, Chat returns the TOKEN_EXPIRED
error.
The Chat SDK triggers the onTokenExpired
callback only when a token expires and the SDK is in the isConnected(true)
state. The callback is triggered only once. When your listener receives this event, retrieve a new token from your token server, and pass this token to Chat with a call to renewToken
.
Although you can use the onTokenExpired
callback to handle token expiration conditions, Agora recommends that you regularly renew the token (for example every hour) to keep the token valid.
If you use Chat together with the Agora RTC SDK, Agora recommends you upgrade to AccessToken2.