Authentication is the act of validating the identity of each user before they access your system. Agora uses digital tokens to authenticate users and their privileges before they access an Agora service, such as joining an Agora call or logging into the real-time messaging system.
To enhance its authentication and security services, Agora provides a new version of token called AccessToken2 as of July 20, 2022. For how to upgrade from AccessToken to AccessToken2, see Upgrade from AccessToken to AccessToken2.
This page shows you how to create a token server and a client app for AccessToken2. The client app retrieves a token from the token server. This token authenticates the current user when the user accesses the Agora service.
The following figure shows the steps in the authentication flow:
A token is a dynamic key generated on your app server that is valid for a maximum of 24 hours. When your users connect to an Agora channel from your app client, Agora Platform validates the token and reads the user and project information stored in the token. A token contains the following information:
In order to follow this procedure you must have the following:
This section shows you how to supply and consume a token that gives rights to specific functionality to authenticated users using the source code provided by Agora.
This section shows you how to get the security information needed to generate a token, including the App ID and App Certificate of your project.
Agora automatically assigns each project an App ID as a unique identifier.
To copy this App ID, find your project on the Project Management page in Agora Console, and click the icon in the App ID column.
To get an App Certificate, do the following:
Token generators create the tokens requested by your client app to enable secure access to Agora security infrastructure. To serve these tokens you deploy a generator in your security infrastructure.
In order to show the authentication workflow, this section shows how to build and run a token server written in Golang on your local machine.
This sample server uses BuildTokenWithUid[1/2]
.
server.go
, with the following content. Then replace <Your App ID>
and <Your App Certificate>
with your App ID and App Certificate.package main
import (
rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
"fmt"
"log"
"net/http"
"encoding/json"
"errors"
"strconv"
)
type rtc_int_token_struct struct{
Uid_rtc_int uint32 `json:"uid"`
Channel_name string `json:"ChannelName"`
Role uint32 `json:"role"`
}
var rtc_token string
var int_uid uint32
var channel_name string
var role_num uint32
var role rtctokenbuilder.Role
// Use RtcTokenBuilder to generate an RTC token.
func generateRtcToken(int_uid uint32, channelName string, role rtctokenbuilder.Role){
appID := "<Your App ID>"
appCertificate := "<Your App Certificate>"
// Number of seconds after which the AccessToken2 expires.
// When the AccessToken2 expires but the privilege does not expire, the user remains in the channel and can continue to publish streams. No callback is triggered from the SDK.
// However, once disconnected from the channel, the user cannot rejoin the channel with that token. Ensure the AccessToken2 does not expire before the privileges.
tokenExpireTimeInSeconds := uint32(40)
// Number of seconds after which the privilege expires.
// The token-privilege-will-expire callback occurs 30 seconds before the privilege expires.
// The token-privilege-did-expire callback occurs when the privilege expires.
// For demonstration purposes the expire time is set to 40 seconds. This shows you the automatic token renew actions of the client.
privilegeExpireTimeInSeconds := uint32(40)
result, err := rtctokenbuilder.BuildTokenWithUid(appID, appCertificate, channelName, int_uid, role, tokenExpireTimeInSeconds, privilegeExpireTimeInSeconds)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("Token with uid: %s\n", result)
fmt.Printf("uid is %d\n", int_uid )
fmt.Printf("ChannelName is %s\n", channelName)
fmt.Printf("Role is %d\n", role)
}
rtc_token = result
}
func rtcTokenHandler(w http.ResponseWriter, r *http.Request){
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS");
w.Header().Set("Access-Control-Allow-Headers", "*");
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
if r.Method != "POST" && r.Method != "OPTIONS" {
http.Error(w, "Unsupported method. Please check.", http.StatusNotFound)
return
}
var t_int rtc_int_token_struct
var unmarshalErr *json.UnmarshalTypeError
int_decoder := json.NewDecoder(r.Body)
int_err := int_decoder.Decode(&t_int)
if (int_err == nil) {
int_uid = t_int.Uid_rtc_int
channel_name = t_int.Channel_name
role_num = t_int.Role
switch role_num {
case 1:
role = rtctokenbuilder.RolePublisher
case 2:
role = rtctokenbuilder.RoleSubscriber
}
}
if (int_err != nil) {
if errors.As(int_err, &unmarshalErr){
errorResponse(w, "Bad request. Wrong type provided for field " + unmarshalErr.Value + unmarshalErr.Field + unmarshalErr.Struct, http.StatusBadRequest)
} else {
errorResponse(w, "Bad request.", http.StatusBadRequest)
}
return
}
generateRtcToken(int_uid, channel_name, role)
errorResponse(w, rtc_token, http.StatusOK)
log.Println(w, r)
}
func errorResponse(w http.ResponseWriter, message string, httpStatusCode int){
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.WriteHeader(httpStatusCode)
resp := make(map[string]string)
resp["token"] = message
resp["code"] = strconv.Itoa(httpStatusCode)
jsonResp, _ := json.Marshal(resp)
w.Write(jsonResp)
}
func main(){
// RTC token from RTC int uid
http.HandleFunc("/fetch_rtc_token", rtcTokenHandler)
fmt.Printf("Starting server at port 8082\n")
if err := http.ListenAndServe(":8082", nil); err != nil {
log.Fatal(err)
}
}
A go.mod
file defines this module’s import path and dependency requirements. To create the go.mod
for your token server, run the following command:
$ go mod init sampleServer
Get dependencies by running the following command:
$ go get
Start the server by running the following command:
$ go run server.go
This section uses the Android client as an example to show how to use a token for client-side user authentication.
In order to show the authentication workflow, this section shows how to build and run an Android client on the simulator of your local machine.
Based on the project you have created in Get Started with Interactive Live Streaming Premium
, add the following dependencies to /Gradle Scripts/build.gradle(Module: <projectname>.app)
:
dependencies {
...
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.google.code.gson:gson:2.8.4'
...
}
Replace the content in MainActivity.java
with the following code. Replace Your App ID
with your App ID. The App ID must match the one in the server. You also need to replace <Your Host URL and port> with the host URL and port of the local Golang server you have just deployed, such as 10.53.3.234:8082.
In the code example, you can see that token is related to the following code logic in the client:
Call joinChannel
to join the channel with token, uid, and channel name. The uid and channel name must be the same as the ones used to generate the token.
The onTokenPrivilegeWillExpire
callback occurs 30 seconds before a token expires. When the onTokenPrivilegeWillExpire
callback is triggered,the client must fetch the token from the server and call renewToken to pass the new token to the SDK.
The onRequestToken
callback occurs when a token expires. When the onRequestToken
callback is triggered, the client must fetch the token from the server and call joinChannel
to use the new token to join the channel.
package com.example.rtcquickstart;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.view.SurfaceView;
import android.widget.FrameLayout;
import android.widget.Toast;
import io.agora.rtc2.Constants;
import io.agora.rtc2.IRtcEngineEventHandler;
import io.agora.rtc2.RtcEngine;
import io.agora.rtc2.video.VideoCanvas;
import io.agora.rtc2.ChannelMediaOptions;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.Call;
import okhttp3.Callback;
import com.google.gson.Gson;
import java.util.Map;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
// Fill the App ID of your project generated on Agora Console.
private String appId = "Your App ID";
// Fill the channel name.
private String channelName = "1234";
private String token = "";
private RtcEngine mRtcEngine;
private int joined = 1;
private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
@Override
// Listen for the remote host joining the channel to get the uid of the host.
public void onUserJoined(int uid, int elapsed) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// Call setupRemoteVideo to set the remote video view after getting uid from the onUserJoined callback.
setupRemoteVideo(uid);
}
});
}
@Override
public void onTokenPrivilegeWillExpire(String token) {
fetchToken(1234, channelName, 1);
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast toast = Toast.makeText(MainActivity.this, "Token renewed", Toast.LENGTH_SHORT);
toast.show();
}
});
super.onTokenPrivilegeWillExpire(token);
}
@Override
public void onRequestToken() {
joined = 1;
fetchToken(1234, channelName, 1);
super.onRequestToken();
}
};
private static final int PERMISSION_REQ_ID = 22;
private static final String[] REQUESTED_PERMISSIONS = {
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA
};
private boolean checkSelfPermission(String permission, int requestCode) {
if (ContextCompat.checkSelfPermission(this, permission) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, requestCode);
return false;
}
return true;
}
// Fetches the RTC token
private void fetchToken(int uid,String channelName,int tokenRole){
OkHttpClient client = new OkHttpClient();
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
JSONObject json = new JSONObject();
try {
json.put("uid", uid);
json.put("ChannelName", channelName);
json.put("role", tokenRole);
} catch (JSONException e) {
e.printStackTrace();
}
RequestBody requestBody = RequestBody.create(JSON, String.valueOf(json));
Request request = new Request.Builder()
.url("http://<Your Host URL and port>/fetch_rtc_token")
.header("Content-Type", "application/json; charset=UTF-8")
.post(requestBody)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if(response.isSuccessful()){
Gson gson = new Gson();
String result = response.body().string();
Map map = gson.fromJson(result, Map.class);
new Thread(new Runnable() {
@Override
public void run() {
token = map.get("token").toString();
// If user has not joined, join the channel with a token.
ChannelMediaOptions options = new ChannelMediaOptions();
if (joined != 0){joined = mRtcEngine.joinChannel(token, channelName, 1234, options);}
// If user has joined, renew the token by calling renewToken
else {mRtcEngine.renewToken(token);}
}
});
}
}
});
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// If all the permissions are granted, initialize the RtcEngine object and join a channel.
if (checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID) &&
checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID)) {
initializeAndJoinChannel();
}
}
protected void onDestroy() {
super.onDestroy();
mRtcEngine.leaveChannel();
mRtcEngine.destroy();
}
private void initializeAndJoinChannel() {
try {
mRtcEngine = RtcEngine.create(getBaseContext(), appId, mRtcEventHandler);
} catch (Exception e) {
throw new RuntimeException("Check the error.");
}
// For a live streaming scenario, set the channel profile as BROADCASTING.
mRtcEngine.setChannelProfile(Constants.CHANNEL_PROFILE_LIVE_BROADCASTING);
// Set the client role as BROADCASTER or AUDIENCE according to the scenario.
mRtcEngine.setClientRole(Constants.CLIENT_ROLE_BROADCASTER);
// By default, video is disabled, and you need to call enableVideo to start a video stream.
mRtcEngine.enableVideo();
FrameLayout container = findViewById(R.id.local_video_view_container);
// Call CreateRendererView to create a SurfaceView object and add it as a child to the FrameLayout.
SurfaceView surfaceView = new SurfaceView (getBaseContext());
container.addView(surfaceView);
// Pass the SurfaceView object to Agora so that it renders the local video.
mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0));
// Start local preview
mRtcEngine.startPreview();
// Fetches the token from token server
fetchToken(1234, channelName, 1);
}
private void setupRemoteVideo(int uid) {
FrameLayout container = findViewById(R.id.remote_video_view_container);
SurfaceView surfaceView = new SurfaceView (getBaseContext());
surfaceView.setZOrderMediaOverlay(true);
container.addView(surfaceView);
mRtcEngine.setupRemoteVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid));
}
}
Build and run the project in your Android simulator in the local machine to perform the following actions:
This section introduces token generator libraries, version requirements, and related documents about AccessToken2.
AccessToken2 supports the following versions of the Agora RTC SDK:
SDK | SDK version to support AccessToken2 |
---|---|
RTC Native 4.x SDK | >= v4.0.0-beta.1 |
RTC Electron 4.x SDK | >= v4.0.0-beta.1 |
RTC Unity 4.x SDK | >= v4.0.0-beta.1 |
RTC React Native 4.x SDK | >= v4.0.0-beta.1 |
RTC Flutter 4.x SDK | >= v6.0.0-beta.1 |
RTC Native 3.x SDK | >= 3.6.0 |
RTC Electron 3.x SDK | >= 3.6.0 |
RTC Unity 3.x SDK | >= 3.6.0 |
RTC React Native 3.x SDK | >= 3.6.0 |
RTC Flutter 3.x SDK | >= 5.10 |
RTC Web SDK | >= v4.8.0 |
RTC SDKs that use AccessToken2 can interoperate with RTC SDKs that use AccessToken. RTC SDKs that support AccessToken2 also support AccessToken.
Agora provides an open-source AgoraDynamicKey repository on GitHub, which enables you to generate AccessToken2 on your server with programming languages such as C++, Java, and Go.
Language | Algorithm | Core method | Sample code |
---|---|---|---|
C++ | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilder2Sample.cpp |
Go | HMAC-SHA256 | buildTokenWithUid | sample.go |
Java | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilder2Sample.java |
Node.js | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilder2Sample.js |
PHP | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilder2Sample.php |
Python | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilder2Sample.py |
Python3 | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilder2Sample.py |
This section introduces the core method for generating AccessToken2: BuildTokenWithUid. The AccessToken2 generator libraries provide two BuildTokenWithUid methods:
BuildTokenWithUid
[1/2]: Generates an AccessToken2, and sets the expiration for AccessToken2 and the expiration for all privileges.
BuildTokenWithUid
[2/2]: Generates an AccessToken2, and sets the expiration for the following:
This method uses a token_expire parameter to set the expiration for AccessToken2 and a privilege_expire parameter to set the expiration for all privileges.
// Take C++ as an example
static std::string BuildTokenWithUid(const std::string& app_id,
const std::string& app_certificate,
const std::string& channel_name,
uint32_t uid, UserRole role,
uint32_t token_expire,
uint32_t privilege_expire = 0);
Parameter | Description |
---|---|
Parameter | Description |
app_id |
The App ID of your Agora project. |
app_certificate |
The App Certificate of your Agora project. |
channel_name |
The channel name. The string length must be less than 64 bytes. Supported character scopes are:
|
uid |
The user ID of the user to be authenticated. A 32-bit unsigned integer with a value range from 1 to (2³² - 1). It must be unique. Set uid as 0, if you do not want to authenticate the user ID, that is, any uid from the app client can join the channel. |
role |
The privilege of the user, either as a publisher or a subscriber. This parameter determines whether a user can publish streams in the channel.Role_Publisher(1) : The user has the privilege of a publisher, that is, the user can publish streams in the channel.Role_Subscriber(2) : The user has the privilege of a subscriber, that is, the user can only subscribe to streams, not publish them, in the channel.This value takes effect only if you have enabled co-host authentication. For details, see Enable co-host authentication. |
token_expire |
The duration (in seconds) from the generation of an AccessToken2 to the expiration of that AccessToken2. For example, if you set it as 600, the AccessToken2 expires 10 minutes after generation. The maximum duration of an AccessToken2 is 24 hours. If you set it to a duration longer than 24 hours, the AccessToken2 still expires after 24 hours. If you set it to 0, the AccessToken2 expires immediately. |
privilege_expire |
The duration (in seconds) from the generation of an AccessToken2 to the expiration of all privileges. For example, if you set it to 600, the privilege expires 10 minutes after generation. If you set it to 0 (default), the privilege never expires. |
To facilitate privilege-level configuration in a channel, Agora provides an overloaded method, BuildTokenWithUid
[2/2], to support configuring the expiration of the AccessToken2 and related privileges:
// Take C++ as an example
static std::string BuildTokenWithUid(
const std::string& app_id,
const std::string& app_certificate,
const std::string& channel_name,
uint32_t uid,
uint32_t token_expire,
uint32_t join_channel_privilege_expire = 0,
uint32_t pub_audio_privilege_expire = 0,
uint32_t pub_video_privilege_expire = 0,
uint32_t pub_data_stream_privilege_expire = 0);
This method generates an RTC AccessToken2 and supports configuring the expiration time of the token and the following privileges:
The privileges of publishing audio streams in an RTC channel, publishing video streams in an RTC channel, and publishing data streams in an RTC channel only take effect after enabling cohost authentication.
You can assign multiple privileges to a user. When a privilege is about to expire or has expired, the RTC SDK triggers the onTokenPriviegeWillExpire
callback or the onRequestToken
callback. You need to take the following actions in your own app logic:
renewToken
to renew the AccessToken2.You need to set an appropriate expiration timestamp. For example, if the expiration time of joining a channel is earlier than that of publishing audio in the channel, when the privilege of joining a channel expires, the user is kicked out of the RTC channel. Even if the privilege of publishing audio is still valid, user cannot exercise that privilege.
Parameter | Description |
---|---|
token_expire |
The duration (in seconds) from the generation of an AccessToken2 to the expiration of that AccessToken2. For example, if you set it as 600, the AccessToken2 expires 10 minutes after generation. The maximum duration of an AccessToken2 is 24 hours. If you set it to a period longer than 24 hours, the AccessToken2 still expires after 24 hours. If you set it to 0, the AccessToken2 expires immediately. |
join_channel_privilege_expire |
The duration (in seconds) from the generation of an AccessToken2 to the expiration of the privilege of joining a channel. For example, if you set it to 600, the privilege expires 10 minutes after generation. If you set it to 0 (default), the privilege never expires. |
pub_audio_privilege_expire |
The duration (in seconds) from the generation of an AccessToken2 to the expiration of the privilege of publishing audio streams in a channel. For example, if you set it to 600, the privilege expires 10 minutes after generation. If you set it to 0 (default), the privilege never expires. |
pub_video_privilege_expire |
The duration (in seconds) from the generation of an AccessToken2 to the expiration of the privilege of publishing video streams in a channel. For example, if you set it to 600, the privilege expires 10 minutes after generation. If you set it to 0 (default), the privilege never expires. |
pub_data_stream_privilege_expire |
The duration (in seconds) from the generation of an AccessToken2 to the expiration of the privilege of publishing data streams in a channel. For example, if you set it to 600, the privilege expires 10 minutes after generation. If you set it to 0 (default), the privilege never expires. |
Refer to the following steps to enable this function in Agora Console:
Co-host authentication takes effect in 5 minutes.
Once you have enabled co-host authentication, a user using your app must meet both of the following requirements to publish streams in a channel:
setClientRole
is set as BROADCASTER(1)
.role
parameter in the BuildTokenWithUid
method as kRolePublisher
).