How to Build a Token Server for Agora Applications using NodeJS
Note: This article was updated on 20-Dec-21 to use v2.0.0 of the Node.js token server.
Security in video chat applications is a hot topic at the moment. As remote working and virtual events become more widespread, the need for security will increase.
In the Agora platform, one layer of security comes in the form of token authentication. A token is a dynamic key that is generated using a set of given inputs. The Agora platform uses tokens to authenticate users.
Agora offers token security for its RTC and RTM SDKs. This guide will explain how to build a simple microservice using Node.js and Express to generate these tokens.
Prerequisites
- An Agora developer account (It’s free! Sign up here)
- A basic understanding of JavaScript ES6, Node.js, and NPM
- A basic of how Express web servers function (minimal knowledge needed)
Project Setup
To start our project, we’ll create a new folder and open a terminal window in this folder. In the terminal, we’ll run npm init
to set up the node project. The create project prompt will appear. I used the default settings, but feel free to customize this portion.
Now that the project has been created, we can add our NPM dependencies ( express, agora-access-token, and dotenv) using:
npm install express agora-access-token dotenv
Build the Express Server
Now that the project is set up, open the folder in your favorite code editor. Looking at the package.json
, you'll notice that the entry file is index.js
. This file doesn't exist in our project, so we'll have to create a new file and name it index.js
.
In the index.js
we'll start by requiring our modules. From Express
, we'll need the Express object, and from agora-access-token
we'll extract references to the RtcTokenBuilder
and RtcRole
objects. We'll also use dotenv
from its package for our environment variables:
const express = require('express');
const {RtcTokenBuilder, RtcRole} = require('agora-access-token');
const dotenv = require('dotenv');
Let’s define our constants by creating a .env
file and adding our Agora credentials and the port we're going to use to listen for requests:
APP_ID=970XXXXX...
APP_CERTIFICATE=5CFXXXXX...
PORT=8080
Next, we’ll define our app
constant that will instantiate our Express
object and allow us to set up our server:
const app = express();
Before we can set up the GET endpoint for our Express server, we’ll need to define the functions that are invoked when the endpoint is accessed. The first function ( nocache
) will apply the response headers, which force the browser to never cache the response, ensuring that we always get a fresh token. You'll notice we call the next()
method at the end because this function is a middleware function that is the first in the series, so we need to call next()
to let Express know to continue to the next function in the series:
const nocache = (_, resp, next) => {
resp.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
resp.header('Expires', '-1');
resp.header('Pragma', 'no-cache');
next();
}
The second function ( generateRTCToken
) will handle the request and return the JSON response. We'll define the function now and add the body once we finish setting up the Express server. This is the last function in the series, so we don't need the next parameter/function:
const generateRTCToken = (req, resp) => { };
Let’s define a GET
endpoint /rtc
, passing in the nochache
and generateRTCToken
functions:
app.get('/rtc/:channel/:role/:tokentype/:uid', nocache , generateRTCToken)
You’ll notice the route contains :<path>
. The colon (:) marks the path as a variable, and the user can pass in values like channel name, user role, type of token, and user UID to the route. We can access this data in our application.
As the last step in creating our Express server, we’ll implement the listen()
method and pass in the port and a callback once the server is ready and listening on the given port:
app.listen(PORT, () => {
console.log(`Listening on port: ${PORT}`);
});
Generate the Agora RTC Token
Now that we have our Express server set up, we are ready to add the functionality to the generateRTCToken
function. We'll start by setting the response header to ensure that we don't run into any CORS issues:
const generateRTCToken = (req, resp) => {
resp.header('Access-Control-Allow-Origin', '*');
Get the Request Parameters
Next, we’ll check for the channel in our request parameters. This is a required parameter, so if channelName
is undefined, we need to return an error with a 500
response code and a JSON object with the error:
const channelName = req.params.channel;
if (!channelName) {
return resp.status(500).json({ 'error': 'channel is required' });
}
For uid
and role
we'll perform similar checks:
let uid = req.params.uid;
if(!uid || uid === '') {
return resp.status(500).json({ 'error': 'uid is required' });
}
// get role
let role;
if (req.params.role === 'publisher') {
role = RtcRole.PUBLISHER;
} else if (req.params.role === 'audience') {
role = RtcRole.SUBSCRIBER
} else {
return resp.status(500).json({ 'error': 'role is incorrect' });
}
Note: The only privilege enforced by the Agora platform by default is the join channel privilege. To enable the enforcement of other privileges, you need to make a request through Agora Support.
The user can optionally pass in an expiry
query parameter that will set the time for the token to expire. We can access the value and check whether it exists. Otherwise, we set a suitable default of 3600 seconds:
let expireTime = req.query.expiry;
if (!expireTime || expireTime === '') {
expireTime = 3600;
} else {
expireTime = parseInt(expireTime, 10);
}
We’ll calculate the expiration time. It needs to be an integer that represents the time since Jan 1, 1970. We’ll use the current time and add our expiration time to it:
const currentTime = Math.floor(Date.now() / 1000);
const privilegeExpireTime = currentTime + expireTime;
Build the Token
Now that we have all the elements for our token, we are ready to use the RtcTokenBuilder
object to generate the token. We'll check the tokenType
and call the appropriate method on the object, passing in the required values:
let token;
if (req.params.tokentype === 'userAccount') {
token = RtcTokenBuilder.buildTokenWithAccount(APP_ID, APP_CERTIFICATE, channelName, uid, role, privilegeExpireTime);
} else if (req.params.tokentype === 'uid') {
token = RtcTokenBuilder.buildTokenWithUid(APP_ID, APP_CERTIFICATE, channelName, uid, role, privilegeExpireTime);
} else {
return resp.status(500).json({ 'error': 'token type is invalid' });
}
Return the Response
The last step in generating our token is returning the JSON response that contains the token:
...
return resp.json({ 'rtcToken': token });
}
Test the Token Server
Let’s go back to our package.json
and add a command in the object. The start command will execute the node index.js
command so that we can run our server instance:
{
"scripts": {
"start": "node index.js"
},
}
Start the server
Let’s go back to our Command Prompt window and use our new command: npm start
Once the server instance is listening, we’ll see “Listening on port: 8080” (or the port in your .env
file) in our terminal window.
Test the endpoint
Now that our server instance is running, let’s open our web browser and test.
For example, pass “test” as the channel
, "publisher" as the role
, and "uid" as the tokenType
with the UID
of "1" :
http://localhost:8080/rtc/test/publisher/uid/1
This will display:
{"rtcToken":"0062ec0d84c41c4442d88ba6f5a2eeb828bIAD9qg4N4hd04MvaY6A72m4BjYmO/7+xnRMinaI0ncLzkAx+f9gAAAAAEACS0zcn9gASXwEAAQCGvRBf"}
Other examples produce a similar output:2
localhost:8080/rtc/test/publisher/uid/1 localhost:8080/rtc/test/publisher/uid/1?expiry=1000 localhost:8080/rtc/test/subscriber/userAccount/ekaansh
RTM Tokens
We can use the same process to configure a route to generate RTM tokens. You can look at the generateRTMToken
function in the finished project for generating RTM tokens. The /rtm
route looks like this, passing in a UID as "1":
http://localhost:8080/rtm/1
The response looks like:
{"rtmToken":"0062ec0d84c41c4442d88ba6f5a2beb828bIAD9qg4N4hd04MvaY6A72m4BjYmO/7+xnRMinaI0ncLzkAx+f9gAAAAAEACS0zcn9gASXwEAAQCGvRBf"}
Conclusion
And just like that, we are done! In case you weren’t coding along or want to see the finished product together, you can find it on GitHub. You can deploy it to Heroku in two clicks using the button in the Readme.
There’s also a version written in Typescript available on the typescript branch. If you see any room for improvement, feel free to fork the repo and make a pull request!
Other Resources
For more information about tokens for Agora applications, you can take a look at the Set up Authentication guide. We also have a token server built with Golang and Gin that you can find here.
If you have any questions, I invite you to join the Agora Developer Slack community and ask them there.