Use tokens

Updated

Retrieve tokens generated by an authentication token server to securely connect to Agora.

To protect your business, it is best practice to authenticate every client that joins a channel. This guide explains how to fetch an authentication token from your token server, use it to join a channel, and renew the token when it expires.

Understand the tech

When a user attempts to connect to an Agora channel, your app retrieves a token from the token server in your security infrastructure. Your app then sends this token to Agora SDRTN® for authentication. Agora SDRTN® reads the information stored in the token to validate the request.

The following figure shows the call flow you implement to create step-up-authentication with Agora Interactive Live Streaming:

Token authentication flow

Prerequisites

Before starting, ensure that you have:

Implement basic authentication

This section shows you how to implement basic authentication by acquiring a token and using it to join a channel.

Use a token to join a channel

The client requests a token from your authentication server corresponding to the user ID and the channel name. You use the received token to join a channel.

// Channel name
String channelId = "xxx";
// User ID
int uid = 0;
// Request a token from the server corresponding to channelId and uid
String token = getToken(channelId, uid);
// Set channel media options
ChannelMediaOptions option = new ChannelMediaOptions();
option.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
// Join a channel
engine.joinChannel(token, channelId, 0, option);

Token expiration

After you join a channel using a token, the SDK triggers an onTokenPrivilegeWillExpire callback, 30 seconds before the token is set to expire.

When the token expires, the SDK triggers an onRequestToken callback. After receiving the callback, you regenerate a new token on the server side, and then update the token in one of the following ways:

Single channel use-case

  • Call renewToken to pass in the newly generated Token (Recommended).

  • Call updateChannelMediaOptions to update the token.

  • Call leaveChannel [2/2] to leave the current channel, and then pass in a new token when calling joinChannel [2/2] to rejoin the channel.

Multi-channel use-case

If you call joinChannelEx to join multiple channels, call the updateChannelMediaOptionsEx method to update the token.

The following sample code demonstrates how to call renewToken to update the token upon receiving an onTokenPrivilegeWillExpire callback notification.

class RtcEngineEventHandlerImpl extends IRtcEngineEventHandler {
  // Callback is triggered when the token is about to expire
  @Override
  public void onTokenPrivilegeWillExpire(String token) {
    super.onTokenPrivilegeWillExpire(token);

    // Request to generate a fresh token
    String token = getToken();
    // Update token
    engine.renewToken(token);
  }
}

Complete sample code

This section presents sample code to implement token authentication in your client.

  1. In /Gradle Scripts/build.gradle(Module: <projectname>.app), add the following dependencies for making HTTP requests to the token server:

    dependencies {
     implementation 'com.squareup.okhttp3:okhttp:3.10.0'
     implementation 'com.google.code.gson:gson:2.8.4'
    }
  2. In MainActivity, replace the content with the following code:

Complete sample code for token authentication

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 in the App ID from Agora console
   private String appId = "Your App ID";
   // Fill in the channel name
   private String channelName = "demo";
   private String token = "";
   private RtcEngine mRtcEngine;
   private int joined = 1;

   private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
     @Override
     public void onUserJoined(int uid, int elapsed) {
        runOnUiThread(() -> setupRemoteVideo(uid));
     }

     @Override
     public void onTokenPrivilegeWillExpire(String token) {
        fetchToken(1234, channelName, 1);
        runOnUiThread(() -> {
         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;
   }

   // Get 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(() -> {
              token = map.get("token").toString();
              ChannelMediaOptions options = new ChannelMediaOptions();
              if (joined != 0) {
               joined = mRtcEngine.joinChannel(token, channelName, 1234, options);
              } else {
               mRtcEngine.renewToken(token);
              }
           }).start();
         }
        }
     });
   }

   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_main);

     if (checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID) &&
         checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID)) {
        initializeAndJoinChannel();
     }
   }

   @Override
   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.");
     }

     mRtcEngine.setChannelProfile(Constants.CHANNEL_PROFILE_LIVE_BROADCASTING);
     mRtcEngine.setClientRole(Constants.CLIENT_ROLE_BROADCASTER);
     mRtcEngine.enableVideo();

     FrameLayout container = findViewById(R.id.local_video_view_container);
     SurfaceView surfaceView = new SurfaceView(getBaseContext());
     container.addView(surfaceView);
     mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0));
     mRtcEngine.startPreview();
     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));
   }
  }

The user ID and channel name used to join a channel must be consistent with the values used to generate the token.

Reference

This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.

API reference