# Use tokens (/en/realtime-media/video/build/authenticate-users/authentication-workflow/android)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

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 [#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 Video Calling:

<Accordions>
  <Accordion title="Token authentication flow">
    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)
  </Accordion>
</Accordions>

## Prerequisites [#prerequisites]

Before starting, ensure that you have:

* Implemented the [Quickstart](/en/realtime-media/video/get-started-sdk) in your project.

* Deployed a token server using either of the following guides:

  * [Deploy a token server](/en/realtime-media/video/build/authenticate-users/deploy-token-server)
  * [Deploy a middleware server](/en/realtime-media/video/build/authenticate-users/middleware-token-server)

## Implement basic authentication [#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 [#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.

    <Tabs defaultValue="java">
      <TabsList>
        <TabsTrigger value="java">
          Java
        </TabsTrigger>

        <TabsTrigger value="kotlin">
          Kotlin
        </TabsTrigger>
      </TabsList>

      <TabsContent value="java">
        ```java
        // 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);
        ```
      </TabsContent>

      <TabsContent value="kotlin">
        ```kotlin
        // Channel name
        val channelId = "xxx"
        // User ID
        val uid = 0
        // Request a token from the server corresponding to channelId and uid
        val token = getToken(channelId, uid)
        // Set channel media options
        val option = ChannelMediaOptions().apply {
            clientRoleType = Constants.CLIENT_ROLE_BROADCASTER
        }
        // Join a channel
        engine.joinChannel(token, channelId, 0, option)
        ```
      </TabsContent>
    </Tabs>

    ### Token expiration [#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 [#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 [#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.

    <Tabs defaultValue="java">
      <TabsList>
        <TabsTrigger value="java">
          Java
        </TabsTrigger>

        <TabsTrigger value="kotlin">
          Kotlin
        </TabsTrigger>
      </TabsList>

      <TabsContent value="java">
        ```java
        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);
            }
        }
        ```
      </TabsContent>

      <TabsContent value="kotlin">
        ```kotlin
        class RtcEngineEventHandlerImpl : IRtcEngineEventHandler() {
            // Callback triggered when the token is about to expire
            override fun onTokenPrivilegeWillExpire(token: String) {
                super.onTokenPrivilegeWillExpire(token)

                // Request to generate a fresh token
                val newToken = getToken()
                // Update the token
                engine.renewToken(newToken)
            }
        }
        ```
      </TabsContent>
    </Tabs>

    ### Complete sample code [#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:

       ```yaml
       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**

    <Tabs defaultValue="java">
      <TabsList>
        <TabsTrigger value="java">
          Java
        </TabsTrigger>

        <TabsTrigger value="kotlin">
          Kotlin
        </TabsTrigger>
      </TabsList>

      <TabsContent value="java">
        ```java
        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));
              }
           }
        ```
      </TabsContent>

      <TabsContent value="kotlin">
        ```kotlin
        package com.example.rtcquickstart

           import android.Manifest;
           import android.content.pm.PackageManager;
           import android.os.Bundle;
           import android.view.SurfaceView;
           import android.widget.FrameLayout;
           import android.widget.Toast;

           import androidx.appcompat.app.AppCompatActivity;
           import androidx.core.app.ActivityCompat;
           import androidx.core.content.ContextCompat;

           import io.agora.rtc2.*;
           import io.agora.rtc2.video.VideoCanvas;

           import com.google.gson.Gson;
           import okhttp3.*;
           import org.json.JSONException;
           import org.json.JSONObject;
           import java.io.IOException;
           import java.util.*;

           class MainActivity : AppCompatActivity() {

              // Fill in the App ID from Agora console
              private val appId = "Your App ID"
              // Fill in the channel name
              private val channelName = "demo"
              private var token = ""
              private var mRtcEngine: RtcEngine? = null
              private var joined = 1

              private val mRtcEventHandler = object : IRtcEngineEventHandler() {
                 override fun onUserJoined(uid: Int, elapsed: Int) {
                       runOnUiThread {
                          // After obtaining the user ID in the onUserJoined callback, call setupRemoteVideo to set the remote user view
                          setupRemoteVideo(uid)
                       }
                 }

                 override fun onTokenPrivilegeWillExpire(token: String) {
                       fetchToken(1234, channelName, 1)
                       runOnUiThread {
                          val toast = Toast.makeText(this@MainActivity, "Token renewed", Toast.LENGTH_SHORT)
                          toast.show()
                       }
                       super.onTokenPrivilegeWillExpire(token)
                 }

                 override fun onRequestToken() {
                       joined = 1
                       fetchToken(1234, channelName, 1)
                       super.onRequestToken()
                 }
              }

              companion object {
                 private const val PERMISSION_REQ_ID = 22
                 private val REQUESTED_PERMISSIONS = arrayOf(
                       Manifest.permission.RECORD_AUDIO,
                       Manifest.permission.CAMERA
                 )
              }

              private fun checkSelfPermission(permission: String, requestCode: Int): Boolean {
                 return if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                       ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, requestCode)
                       false
                 } else {
                       true
                 }
              }

              // Get RTC Token
              private fun fetchToken(uid: Int, channelName: String, tokenRole: Int) {
                 val client = OkHttpClient()
                 val JSON = MediaType.parse("application/json; charset=utf-8")
                 val json = JSONObject()
                 try {
                       json.put("uid", uid)
                       json.put("ChannelName", channelName)
                       json.put("role", tokenRole)
                 } catch (e: JSONException) {
                       e.printStackTrace()
                 }

                 val requestBody = RequestBody.create(JSON, json.toString())
                 val request = Request.Builder()
                       .url("http://<Your Host URL and port>/fetch_rtc_token")
                       .header("Content-Type", "application/json; charset=UTF-8")
                       .post(requestBody)
                       .build()

                 client.newCall(request).enqueue(object : Callback {
                       override fun onFailure(call: Call, e: IOException) {
                          // Handle failure
                       }

                       override fun onResponse(call: Call, response: Response) {
                          if (response.isSuccessful) {
                             val gson = Gson()
                             val result = response.body?.string()
                             val map: Map<String, Any> = gson.fromJson(result, Map::class.java)
                             Thread {
                                   token = map["token"].toString()
                                   // If the user has not joined the channel, use token to join the channel
                                   val options = ChannelMediaOptions()
                                   if (joined != 0) {
                                      joined = mRtcEngine?.joinChannel(token, channelName, 1234, options) ?: -1
                                   } else {
                                      mRtcEngine?.renewToken(token)
                                   }
                             }.start()
                          }
                       }
                 })
              }

              override fun onCreate(savedInstanceState: Bundle?) {
                 super.onCreate(savedInstanceState)
                 setContentView(R.layout.activity_main)

                 // If all permissions are granted, initialize the RtcEngine object and join the channel
                 if (checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID) &&
                       checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID)) {
                       initializeAndJoinChannel()
                 }
              }

              override fun onDestroy() {
                 super.onDestroy()
                 mRtcEngine?.leaveChannel()
                 mRtcEngine?.destroy()
              }

              private fun initializeAndJoinChannel() {
                 try {
                       mRtcEngine = RtcEngine.create(baseContext, appId, mRtcEventHandler)
                 } catch (e: Exception) {
                       throw RuntimeException("Check the error.")
                 }

                 // In interactive live broadcast, set the channel scene to BROADCASTING
                 mRtcEngine?.setChannelProfile(Constants.CHANNEL_PROFILE_LIVE_BROADCASTING)
                 // Depending on the actual situation, set the user role to BROADCASTER or AUDIENCE
                 mRtcEngine?.setClientRole(Constants.CLIENT_ROLE_BROADCASTER)

                 // SDK turns off video by default. Call enableVideo to enable video
                 mRtcEngine?.enableVideo()

                 val container: FrameLayout = findViewById(R.id.local_video_view_container)
                 // Call CreateRendererView to create a SurfaceView object and add it as a child to the FrameLayout
                 val surfaceView = SurfaceView(baseContext)
                 container.addView(surfaceView)
                 // Pass the SurfaceView object to Agora to render local video
                 mRtcEngine?.setupLocalVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0))
                 // Start local video preview
                 mRtcEngine?.startPreview()
                 // Get a token from the token server
                 fetchToken(1234, channelName, 1)
              }

              private fun setupRemoteVideo(uid: Int) {
                 val container: FrameLayout = findViewById(R.id.remote_video_view_container)
                 val surfaceView = SurfaceView(baseContext)
                 surfaceView.setZOrderMediaOverlay(true)
                 container.addView(surfaceView)
                 mRtcEngine?.setupRemoteVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid))
              }
           }
        ```
      </TabsContent>
    </Tabs>

    
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
<CalloutContainer type="warning">
  <CalloutTitle>
    Note
  </CalloutTitle>

  <CalloutDescription>
    The user ID and channel name used to join a channel must be consistent with the values used to generate the token.
  </CalloutDescription>
</CalloutContainer>

## Reference [#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 [#api-reference]

    * [`joinChannel` \[2/2\]](https://api-ref.agora.io/en/video-sdk/android/4.x/API/class_irtcengine.html#api_irtcengine_joinchannel2)

    * [`renewToken`](https://api-ref.agora.io/en/video-sdk/android/4.x/API/class_irtcengine.html#api_irtcengine_renewtoken)

    * [`onTokenPrivilegeWillExpire`](https://api-ref.agora.io/en/video-sdk/android/4.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_ontokenprivilegewillexpire)

    
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
