# Use tokens (/en/realtime-media/voice/build/set-up-token-authentication/use-tokens)

> 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]

<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="[&#x22;android&#x22;,&#x22;ios&#x22;,&#x22;macos&#x22;,&#x22;web&#x22;,&#x22;windows&#x22;,&#x22;electron&#x22;,&#x22;flutter&#x22;,&#x22;react-native&#x22;,&#x22;javascript&#x22;,&#x22;unity&#x22;,&#x22;unreal&#x22;,&#x22;blueprint&#x22;,&#x22;python&#x22;,&#x22;linux-cpp&#x22;,&#x22;linux-c&#x22;]" showTabs="true">
  <_PlatformPanel platform="android">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />

    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 Voice Calling:

    **Token authentication flow**

    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)

    ## Prerequisites [#prerequisites]

    Before starting, ensure that you have:

    * Implemented the [Quickstart](../index.mdx) in your project.

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

      * [Deploy a token server](deploy-token-server.mdx)
      * [Deploy a middleware server](middleware-token-server.md)

    ## 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.

    <CodeBlockTabs defaultValue="java">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="java">
          Java
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="kotlin">
          Kotlin
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab 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);
        ```
      </CodeBlockTab>

      <CodeBlockTab 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)
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    ### 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.

    <CodeBlockTabs defaultValue="java">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="java">
          Java
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="kotlin">
          Kotlin
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab 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);
          }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab 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)
          }
        }
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    ### 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**

    <CodeBlockTabs defaultValue="java">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="java">
          Java
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="kotlin">
          Kotlin
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab 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));
           }
          }
        ```
      </CodeBlockTab>

      <CodeBlockTab 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))
           }
          }
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    <CalloutContainer type="info">
      <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)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="ios">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />

    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 Voice Calling:

    **Token authentication flow**

    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)

    ## Prerequisites [#prerequisites-1]

    Before starting, ensure that you have:

    * Implemented the [Quickstart](../index.mdx) in your project.

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

      * [Deploy a token server](deploy-token-server.mdx)
      * [Deploy a middleware server](middleware-token-server.md)

    ## Implement basic authentication [#implement-basic-authentication-1]

    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-1]

    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.

    ```swift
    // Channel name
    let channelId: String = "xxxx"
    // User ID
    let uid: UInt = 0
    // Request the server to generate a token corresponding to channelId and uid
    let token = getToken()
    // Set channel media options
    let mediaOption = AgoraRtcChannelMediaOptions()
    // Set the user role as host
    mediaOption.clientRoleType = .broadcaster
    // Use the token to join a channel
    agoraKit.joinChannel(byToken: token, channelId: channelId, uid: uid, mediaOptions: mediaOption)
    ```

    ### Token expiration [#token-expiration-1]

    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-1]

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

    * Call `updateChannelWithMediaOptions` to update the token.

    * Call `leaveChannel` \[2/2] to leave the current channel, and then pass in a new token when calling `joinChannelByToken` \[2/4] to rejoin the channel.

    #### Multi-channel use-case [#multi-channel-use-case-1]

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

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

    ```swift
    extension JoinChannelVideoToken: AgoraRtcEngineDelegate {
      // Callback is triggered when the token is about to expire
      func rtcEngine(_ engine: AgoraRtcEngineKit, tokenPrivilegeWillExpire token: String) {
        // Request to generate a fresh token
        let token = getToken()
        // Update token
        engine.renewToken(token)
      }
    }
    ```

    ### Complete sample code [#complete-sample-code-1]

    For a complete implementation of token authentication, replace the content in `ViewController.swift` with the following code. Replace `Your App ID` with your App ID and `<Your Host URL and port>` with the host URL and port of the local Golang server you have deployed. For example, 123.1.23.123:8082.

    **Complete sample code for token authentication**

    ```java
    import UIKit
    import AgoraRtcKit
    import Foundation

    public enum TokenError: Error{
      case noData
      case invalidData
    }

    class ViewController: UIViewController {
      var localView: UIView!
      var remoteView: UIView!

      var agoraKit: AgoraRtcEngineKit!

      override func viewDidLoad() {
        super.viewDidLoad()
        //After loading the view, you can make other settings
        initView()
        initializeAgoraEngine()
        setClientRole()
        setupLocalVideo()
        fetchToken(channelName: "test", userId: 1234, role: 1){ result in
          switch result {
          case .success(let token):
            print("token is: \(token)")
            self.joinChannel(token: token)
          case .failure(let err):
            print("Could not fetch token: \(err)")
          }
        }
      }

      override func viewDidLayoutSubviews(){
        super.viewDidLayoutSubviews()
        remoteView.frame = self.view.bounds
        localView.frame = CGRect(x: self.view.bounds.width - 90, y: 0, width: 90, height: 160)
      }

      override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(true)
        leaveChannel()
        destroy()
      }

      func initView(){
        remoteView = UIView()
        self.view.addSubview(remoteView)
        localView = UIView()
        self.view.addSubview(localView)
      }

      func initializeAgoraEngine(){
        let config = AgoraRtcEngineConfig()
        config.appId = "Your App ID"
        config.channelProfile = .liveBroadcasting
        agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
        if agoraKit != nil{
          print("Initialization successful")
        }
        else{
          print("Initialization failed")
        }
      }

      func setClientRole(){
        agoraKit.setClientRole(.broadcaster)
      }

      func setupLocalVideo(){
        agoraKit.enableVideo()
        agoraKit.startPreview()
        let videoCanvas = AgoraRtcVideoCanvas()
        videoCanvas.uid = 0
        videoCanvas.renderMode = .hidden
        videoCanvas.view = localView
        agoraKit.setupLocalVideo(videoCanvas)
      }

      func joinChannel(token:String){
        let option = AgoraRtcChannelMediaOptions()
        agoraKit.joinChannel(byToken: token, channelId: "test", uid: 123456, mediaOptions: option)
      }

      func leaveChannel(){
        agoraKit.stopPreview()
        agoraKit.leaveChannel(nil)
      }

      func destroy(){
        AgoraRtcEngineKit.destroy()
      }

      func fetchToken(channelName: String, userId: UInt, role: UInt,
        callback: @escaping (Result<String, Error>) -> Void
      ){
        let url = URL(string: "http://<Your Host URL and port>/fetch_rtc_token")
        let parameters = ["uid":userId,"channelName": channelName, "role": role] as [String : Any]

        print(parameters.self)

        var request = URLRequest(
          url: url!,
          timeoutInterval: 10
        )

        request.httpMethod = "POST"

        do {
          request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
        }
        catch let error {
          print(error.localizedDescription)
        }

        URLSession.shared.dataTask(with: request){data, _, err in
          guard let data = data else {
            if let err = err {
              callback(.failure(err))
            }
            else {
              callback(.failure(TokenError.noData))
            }
          return
        }

        let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])

        if let responseDict = responseJSON as? [String: Any], let token = responseDict["token"] as? String {
          callback(.success(token))
        } else {
          callback(.failure(TokenError.invalidData))
        }

      }.resume()
    }
    }

    extension ViewController: AgoraRtcEngineDelegate{
      func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int){
        let videoCanvas = AgoraRtcVideoCanvas()
        videoCanvas.uid = uid
        videoCanvas.renderMode = .hidden
        videoCanvas.view = remoteView
        agoraKit.setupRemoteVideo(videoCanvas)
      }

      func rtcEngine(_ engine: AgoraRtcEngineKit, tokenPrivilegeWillExpire token: String) {
        self.fetchToken(channelName: "test", userId: 1234, role: 1){ result in
          switch result {
          case .success(let token):
            print("token is: \(token)")
            self.agoraKit.renewToken(token)
            print("Renewed the token")
          case .failure(let err):
            print("Could not fetch token: \(err)")
          }
        }
      }

      func rtcEngine(_ engine: AgoraRtcEngineKit, connectionStateChanged state: AgoraConnectionState, reason: AgoraConnectionChangedReason) {
        print("Connection state changed to")
        print(state.rawValue)
      }

      func rtcEngineRequestToken(_ engine: AgoraRtcEngineKit) {
        fetchToken(channelName: "test", userId: 1234, role: 1){ result in
          switch result {
            case .success(let token):
              print("token is: \(token)")
              self.joinChannel(token: token)
            case .failure(let err):
              print("Could not fetch token: \(err)")
            }
        }
      }
    }
    ```

    <CalloutContainer type="info">
      <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-1]

    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-1]

    * [`joinChannel`](https://api-ref.agora.io/en/video-sdk/ios/4.x/documentation/agorartckit/agorartcenginekit/joinchannel\(bytoken\:channelid\:info\:uid\:joinsuccess:\))

    * [`renewToken`](https://api-ref.agora.io/en/video-sdk/ios/4.x/documentation/agorartckit/agorartcenginekit/renewtoken\(_:\))

    * [`rtcEngine(_:tokenPrivilegeWillExpire:)`](https://api-ref.agora.io/en/video-sdk/ios/4.x/documentation/agorartckit/agorartcenginedelegate/rtcengine\(_\:tokenprivilegewillexpire:\))

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="macos">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="macos" />

    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 Voice Calling:

    **Token authentication flow**

    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)

    ## Prerequisites [#prerequisites-2]

    Before starting, ensure that you have:

    * Implemented the [Quickstart](../index.mdx) in your project.

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

      * [Deploy a token server](deploy-token-server.mdx)
      * [Deploy a middleware server](middleware-token-server.md)

    ## Implement basic authentication [#implement-basic-authentication-2]

    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-2]

    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.

    ```swift
    // Channel name
    let channelId: String = "xxxx"
    // User ID
    let uid: UInt = 0
    // Request the server to generate a token corresponding to channelId and uid
    let token = getToken()
    // Set channel media options
    let mediaOption = AgoraRtcChannelMediaOptions()
    // Set the user role as host
    mediaOption.clientRoleType = .broadcaster
    // Use the token to join a channel
    agoraKit.joinChannel(byToken: token, channelId: channelId, uid: uid, mediaOptions: mediaOption)
    ```

    ### Token expiration [#token-expiration-2]

    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-2]

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

    * Call `updateChannelWithMediaOptions` to update the token.

    * Call `leaveChannel` \[2/2] to leave the current channel, and then pass in a new token when calling `joinChannelByToken` \[2/4] to rejoin the channel.

    #### Multi-channel use-case [#multi-channel-use-case-2]

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

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

    ```swift
    extension JoinChannelVideoToken: AgoraRtcEngineDelegate {
      // Callback is triggered when the token is about to expire
      func rtcEngine(_ engine: AgoraRtcEngineKit, tokenPrivilegeWillExpire token: String) {
        // Request to generate a fresh token
        let token = getToken()
        // Update token
        engine.renewToken(token)
      }
    }
    ```

    ### Complete sample code [#complete-sample-code-2]

    For a complete implementation of token authentication, replace the content in `ViewController.swift` with the following code. Replace `Your App ID` with your App ID and `<Your Host URL and port>` with the host URL and port of the local Golang server you have deployed. For example, 123.1.23.123:8082.

    **Complete sample code for token authentication**

    ```java
    import UIKit
    import AgoraRtcKit
    import Foundation

    public enum TokenError: Error{
      case noData
      case invalidData
    }

    class ViewController: UIViewController {
      var localView: UIView!
      var remoteView: UIView!

      var agoraKit: AgoraRtcEngineKit!

      override func viewDidLoad() {
        super.viewDidLoad()
        //After loading the view, you can make other settings
        initView()
        initializeAgoraEngine()
        setClientRole()
        setupLocalVideo()
        fetchToken(channelName: "test", userId: 1234, role: 1){ result in
          switch result {
          case .success(let token):
            print("token is: \(token)")
            self.joinChannel(token: token)
          case .failure(let err):
            print("Could not fetch token: \(err)")
          }
        }
      }

      override func viewDidLayoutSubviews(){
        super.viewDidLayoutSubviews()
        remoteView.frame = self.view.bounds
        localView.frame = CGRect(x: self.view.bounds.width - 90, y: 0, width: 90, height: 160)
      }

      override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(true)
        leaveChannel()
        destroy()
      }

      func initView(){
        remoteView = UIView()
        self.view.addSubview(remoteView)
        localView = UIView()
        self.view.addSubview(localView)
      }

      func initializeAgoraEngine(){
        let config = AgoraRtcEngineConfig()
        config.appId = "Your App ID"
        config.channelProfile = .liveBroadcasting
        agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
        if agoraKit != nil{
          print("Initialization successful")
        }
        else{
          print("Initialization failed")
        }
      }

      func setClientRole(){
        agoraKit.setClientRole(.broadcaster)
      }

      func setupLocalVideo(){
        agoraKit.enableVideo()
        agoraKit.startPreview()
        let videoCanvas = AgoraRtcVideoCanvas()
        videoCanvas.uid = 0
        videoCanvas.renderMode = .hidden
        videoCanvas.view = localView
        agoraKit.setupLocalVideo(videoCanvas)
      }

      func joinChannel(token:String){
        let option = AgoraRtcChannelMediaOptions()
        agoraKit.joinChannel(byToken: token, channelId: "test", uid: 123456, mediaOptions: option)
      }

      func leaveChannel(){
        agoraKit.stopPreview()
        agoraKit.leaveChannel(nil)
      }

      func destroy(){
        AgoraRtcEngineKit.destroy()
      }

      func fetchToken(channelName: String, userId: UInt, role: UInt,
        callback: @escaping (Result<String, Error>) -> Void
      ){
        let url = URL(string: "http://<Your Host URL and port>/fetch_rtc_token")
        let parameters = ["uid":userId,"channelName": channelName, "role": role] as [String : Any]

        print(parameters.self)

        var request = URLRequest(
          url: url!,
          timeoutInterval: 10
        )

        request.httpMethod = "POST"

        do {
          request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
        }
        catch let error {
          print(error.localizedDescription)
        }

        URLSession.shared.dataTask(with: request){data, _, err in
          guard let data = data else {
            if let err = err {
              callback(.failure(err))
            }
            else {
              callback(.failure(TokenError.noData))
            }
          return
        }

        let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])

        if let responseDict = responseJSON as? [String: Any], let token = responseDict["token"] as? String {
          callback(.success(token))
        } else {
          callback(.failure(TokenError.invalidData))
        }

      }.resume()
    }
    }

    extension ViewController: AgoraRtcEngineDelegate{
      func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int){
        let videoCanvas = AgoraRtcVideoCanvas()
        videoCanvas.uid = uid
        videoCanvas.renderMode = .hidden
        videoCanvas.view = remoteView
        agoraKit.setupRemoteVideo(videoCanvas)
      }

      func rtcEngine(_ engine: AgoraRtcEngineKit, tokenPrivilegeWillExpire token: String) {
        self.fetchToken(channelName: "test", userId: 1234, role: 1){ result in
          switch result {
          case .success(let token):
            print("token is: \(token)")
            self.agoraKit.renewToken(token)
            print("Renewed the token")
          case .failure(let err):
            print("Could not fetch token: \(err)")
          }
        }
      }

      func rtcEngine(_ engine: AgoraRtcEngineKit, connectionStateChanged state: AgoraConnectionState, reason: AgoraConnectionChangedReason) {
        print("Connection state changed to")
        print(state.rawValue)
      }

      func rtcEngineRequestToken(_ engine: AgoraRtcEngineKit) {
        fetchToken(channelName: "test", userId: 1234, role: 1){ result in
          switch result {
            case .success(let token):
              print("token is: \(token)")
              self.joinChannel(token: token)
            case .failure(let err):
              print("Could not fetch token: \(err)")
            }
        }
      }
    }
    ```

    <CalloutContainer type="info">
      <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-2]

    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-2]

    * [`joinChannel`](https://api-ref.agora.io/en/video-sdk/macos/4.x/documentation/agorartckit/agorartcenginekit/joinchannel\(bytoken\:channelid\:info\:uid\:joinsuccess:\))

    * [`renewToken`](https://api-ref.agora.io/en/video-sdk/macos/4.x/documentation/agorartckit/agorartcenginekit/renewtoken\(_:\))

    * [`rtcEngine(_:tokenPrivilegeWillExpire:)`](https://api-ref.agora.io/en/video-sdk/macos/4.x/documentation/agorartckit/agorartcenginedelegate/rtcengine\(_\:tokenprivilegewillexpire:\))

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="web">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />

    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 Voice Calling:

    **Token authentication flow**

    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)

    ## Prerequisites [#prerequisites-3]

    Before starting, ensure that you have:

    * Implemented the [Quickstart](../index.mdx) in your project.

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

      * [Deploy a token server](deploy-token-server.mdx)
      * [Deploy a middleware server](middleware-token-server.md)

    ## Implement basic authentication [#implement-basic-authentication-3]

    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-3]

    This section shows you how to integrate token authentication in your app.

    1. Open the [Quickstart](../index.mdx) project you created earlier.

    2. In `index.html`, include an HTTP client library such as `axios` for sending token requests to the authentication server.

       ```html
       <html>
         <head>
           <title>Token demo</title>
         </head>
         <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
         <body>
           <h1>Token demo</h1>
           <script src="https://download.agora.io/sdk/release/AgoraRTC_N-4.24.5.js"></script>
           <script src="../agoraLogic.js"></script>
         </body>
       </html>
       ```

    3. Add a `fetchToken` method to retrieve a token from your token server to join a channel.

       ```js
       // Retrieve a token from your token server
       function fetchToken(uid, channelName, tokenRole) {

         return new Promise(function (resolve) {
           axios.post('http://<Your host URL and port>/fetch_rtc_token', {
             uid: uid,
             channelName: channelName,
             role: tokenRole
           }, {
             headers: {
               'Content-Type': 'application/json; charset=UTF-8'
             }
           })
             .then(function (response) {
               const token = response.data.token;
               resolve(token);
             })
             .catch(function (error) {
               console.log(error);
             });
         })
       }
       ```

    4. Use the token to join a channel

       ```js
       // Assign the obtained token to the token parameter in the join method
       let token = await fetchToken(uid, options.channel, 1);
       await client.join(options.appId, options.channel, token, uid);
       ```

    ### Token expiration [#token-expiration-3]

    After you join a channel using a token, the SDK triggers the `token-privilege-will-expire` callback, 30 seconds before the token is set to expire. Upon receiving this callback, retrieve a fresh token from the server and call the `renewToken` method to pass the newly generated token to the SDK.

    ```javascript
    client.on("token-privilege-will-expire", async function () {
      // When you receive the token-privilege-will-expire callback, request a fresh token from the server
      let token = await fetchToken(uid, options.channel, 1);
      // Call renewToken to pass the new token to the SDK
      await client.renewToken(token);
    });
    ```

    When the token expires, the SDK triggers the `token-privilege-did-expire` callback. In this case, retrieve a fresh token from the server and call the `join` method to rejoin the channel with the new token:

    ```javascript
    // The token expired.
    client.on("token-privilege-did-expire", async function () {
    console.log("Fetching a new token")
    // Request a new token from the server
    let token = await fetchToken(uid, options.channel, 1);
    console.log("Rejoining the channel with new token")
    // Call join to rejoin the channel
    await client.join(options.appId, options.channel, token, uid);
    });
    ```

    ### Complete sample code [#complete-sample-code-3]

    For a complete implementation of token authentication, refer to the following code:

    **Sample code for basic authentication**

    ```js
      var rtc = {
          // Set local audio track and video track
          localAudioTrack: null,
          localVideoTrack: null,
        };

        var options = {
          // Fill in app ID
          appId: "<Your app ID>",
          // Fill in a channel name
          channel: "test",
          // Set the user as host or audience
          role: "host"
        };

        // Get a token from your token server

        function fetchToken(uid, channelName, tokenRole) {

          return new Promise(function (resolve) {
            axios.post('http://<Your host URL and port>/fetch_rtc_token', {
              uid: uid,
              channelName: channelName,
              role: tokenRole
            }, {
              headers: {
                'Content-Type': 'application/json; charset=UTF-8'
              }
            })
              .then(function (response) {
                const token = response.data.token;
                resolve(token);
              })
              .catch(function (error) {
                console.log(error);
              });
          })
        }

        async function startBasicCall() {

          const client = AgoraRTC.createClient({ mode: "live", codec: "vp8" });
          client.setClientRole(options.role);
          const uid = 123456;

          // Assign the obtained token to the token parameter of the join method, and join the channel
          let token = await fetchToken(uid, options.channel, 1);

          await client.join(options.appId, options.channel, token, uid);
          rtc.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
          rtc.localVideoTrack = await AgoraRTC.createCameraVideoTrack();
          await client.publish([rtc.localAudioTrack, rtc.localVideoTrack]);
          const localPlayerContainer = document.createElement("div");
          localPlayerContainer.id = uid;
          localPlayerContainer.style.width = "640px";
          localPlayerContainer.style.height = "480px";
          document.body.append(localPlayerContainer);

          rtc.localVideoTrack.play(localPlayerContainer);

          console.log("publish success!");

          client.on("user-published", async (user, mediaType) => {
            await client.subscribe(user, mediaType);
            console.log("subscribe success");

            if (mediaType === "video") {
              const remoteVideoTrack = user.videoTrack;
              const remotePlayerContainer = document.createElement("div");
              remotePlayerContainer.textContent = "Remote user " + user.uid.toString();
              remotePlayerContainer.style.width = "640px";
              remotePlayerContainer.style.height = "480px";
              document.body.append(remotePlayerContainer);
              remoteVideoTrack.play(remotePlayerContainer);

            }

            if (mediaType === "audio") {
              const remoteAudioTrack = user.audioTrack;
              remoteAudioTrack.play();
            }

            client.on("user-unpublished", user => {
              const remotePlayerContainer = document.getElementById(user.uid);
              remotePlayerContainer.remove();
            });

          });

          // When you receive the token-privilege-will-expire callback, request a new token from the server and call renewToken to pass the new token to the SDK
          client.on("token-privilege-will-expire", async function () {
            let token = await fetchToken(uid, options.channel, 1);
            await client.renewToken(token);
          });

          // When you receive the token-privilege-did-expire callback, request a new token from the server and call join to rejoin the channel.
          client.on("token-privilege-did-expire", async function () {
            console.log("Fetching a new token")
            let token = await fetchToken(uid, options.channel, 1);
            console.log("Rejoining the channel with a new token")
            await client.join(options.appId, options.channel, token, uid);
          });

        }

        startBasicCall()
    ```

    Replace `<Your app ID>` with your app ID, which must be consistent with the app ID you specified in the server configuration. Update `<Your Host URL and port>` with the host URL and port of the local Golang server you have deployed. For example `99.9.9.99:8082`.

    Build and run the project on the local device, the app performs the following operations:

    * Obtains a token from your token server.
    * Joins the channel.
    * Automatically renews the token when it is about to expire.

    <CalloutContainer type="info">
      <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-3]

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

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="windows">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />

    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 Voice Calling:

    **Token authentication flow**

    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)

    ## Prerequisites [#prerequisites-4]

    Before starting, ensure that you have:

    * Implemented the [Quickstart](../index.mdx) in your project.

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

      * [Deploy a token server](deploy-token-server.mdx)
      * [Deploy a middleware server](middleware-token-server.md)

    ## Implement basic authentication [#implement-basic-authentication-4]

    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-4]

    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.

    ```cpp
    // Channel name
    const char* channelId;
    // User ID
    int uid = 0
    // Request the server to generate a token corresponding to channelId and uid
    const char* token = getToken();
    // Set channel media options
    ChannelMediaOptions options;
    // Set the user role as host
    options.clientRoleType = CLIENT_ROLE_TYPE::CLIENT_ROLE_BROADCASTER;
    // Use the token to join a channel
    m_rtcEngine->joinChannel(token, channelId, uid, options);
    ```

    ### Token expiration [#token-expiration-4]

    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-3]

    * 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-3]

    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.

    ```cpp
    class CJoinChannelVideoByTokenRtcEngineEventHandler
      : public IRtcEngineEventHandler
    {
    public:
      // Callback is triggered when the token is about to expire
      virtual void onTokenPrivilegeWillExpire(const char* token) {
        // Request to generate a fresh token
        const char* newToken = getToken();
        // Update token
        m_rtcEngine->renewToken(newToken);
      }
    };
    ```

    <CalloutContainer type="info">
      <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-4]

    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-3]

    * [joinChannel](https://api-ref.agora.io/en/video-sdk/cpp/4.x/API/class_irtcengine.html#api_irtcengine_joinchannel)

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

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

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="electron">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="electron" />

    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 Voice Calling:

    **Token authentication flow**

    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)

    ## Prerequisites [#prerequisites-5]

    Before starting, ensure that you have:

    * Implemented the [Quickstart](../index.mdx) in your project.

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

      * [Deploy a token server](deploy-token-server.mdx)
      * [Deploy a middleware server](middleware-token-server.md)

    ## Implement basic authentication [#implement-basic-authentication-5]

    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-5]

    This section shows you how to integrate token authentication in your app.

    1. Open the [SDK quickstart](../index.mdx) project you created earlier.

    2. In `renderer.js` replace the contents with the following:

       **Sample code for basic authentication**

       ```dart
         const {
             createAgoraRtcEngine,
             VideoMirrorModeType,
             VideoSourceType,
             RenderModeType,
             ChannelProfileType,
             ClientRoleType,
           } = require("agora-electron-sdk");

           let rtcEngine;
           let localVideoContainer;
           let remoteVideoContainer;
           let isJoined = false;

           const EventHandles = {
             // Listen to local user joining channel events
             onJoinChannelSuccess: ({ channelId, localUid }, elapsed) => {
               console.log('Successfully joined the channel：' + channelId);
               isJoined = true;
               // After local users join the channel, set the local video window
               rtcEngine.setupLocalVideo({
                 sourceType: VideoSourceType.VideoSourceCameraPrimary,
                 view: localVideoContainer,
                 mirrorMode: VideoMirrorModeType.VideoMirrorModeDisabled,
                 renderMode: RenderModeType.RenderModeFit,
               });
             },

             onLeaveChannel: ({ channelId, localUid }, stats) => {
               console.log('Successfully left the channel:' + channelId);
               isJoined = false;
             },

             // Listen to remote user join event
             onUserJoined: ({ channelId, localUid }, remoteUid, elapsed) => {
               console.log('Remote user ' + remoteUid + ' 已加入');
               // Listen to remote user join event
               rtcEngine.setupRemoteVideoEx(
                 {
                   sourceType: VideoSourceType.VideoSourceRemote,
                   uid: remoteUid,
                   view: remoteVideoContainer,
                   mirrorMode: VideoMirrorModeType.VideoMirrorModeDisabled,
                   renderMode: RenderModeType.RenderModeFit,
                 },
                 { channelId },
               );
             },

             // Listen for the token expiration event
             onTokenPrivilegeWillExpire: ({ channelId, localUid }, token) => {
               console.log('token ' + token + '即将过期');
               // Obtain a new token from the server
               fetchToken(channelId, localUid, ClientRoleType.ClientRoleBroadcaster)
             },

             // Listen for the token expired event
             onRequestToken: ({ channelId, localUid }) => {
               console.log('token expired');
               token = ''
             }
           };

           // Send a token request to the token server
           function fetchToken(channelName, uid, role) {
             //Token server URL example: http://12.123.1.123:8082/fetch_rtc_token
             const url = '<Your host URL and port>/fetch_rtc_token';
             const body = JSON.stringify({
               uid,
               ChannelName: channelName,
               role
             });
             fetch(url, {
               method: 'POST',
               body,
             })
               .then((res) => res.json())
               .then((res) => {
                 console.log('token ' + res.token);
                 if (+res.code === 200) {
                   if (isJoined) {
                     rtcEngine.renewToken(res.token)
                   } else {
                     rtcEngine.joinChannel(res.token, channelName, uid, { clientRoleType: role })
                   }
                 }
                 return res;
               });
           }

           window.onload = () => {
             const os = require("os");
             const path = require("path");

             // Fill in your app ID
             const APPID = "<Your app ID>";
             // Fill in the channel name
             const channel = "Test";

             localVideoContainer = document.getElementById("join-channel-local-video");
             remoteVideoContainer = document.getElementById("join-channel-remote-video");
             const sdkLogPath = path.resolve(os.homedir(), "./test.log");

             // Create an RtcEngine instance
             rtcEngine = createAgoraRtcEngine();

             // Initialize the RtcEngine instance
             rtcEngine.initialize({
               appId: APPID,
               logConfig: { filePath: sdkLogPath }
             });

             // Register event handler
             rtcEngine.registerEventHandler(EventHandles);

             //Set the channel profile to live broadcast
             rtcEngine.setChannelProfile(ChannelProfileType.ChannelProfileLiveBroadcasting);

             // Set the user role, for host use ClientRoleBroadcaster, for audience use ClientRoleAudience
             rtcEngine.setClientRole(ClientRoleType.ClientRoleBroadcaster);

             // Enable the video module
             rtcEngine.enableVideo();

             // Enable camera preview
             rtcEngine.startPreview();

             // Use the token to join a channel
             // You need to specify the user ID yourself and ensure its uniqueness within the channel
             fetchToken(channel, 123456, ClientRoleType.ClientRoleBroadcaster);
           };
       ```

       Replace `<Your app ID>` with your app ID, which must be consistent with the app ID you specified in the server configuration. Update `<Your host URL and port>` with the host URL and port of the local Golang server you have deployed. For example `99.9.9.99:8082`.

    The sample code implements the following logic:

    * Calls `joinChannel` to join a channel using the user ID, the channel name, and a token you obtain from the server. The user ID and channel name you specify must be consistent with the values you used to generate the token.

    * The SDK triggers an `onTokenPrivilegeWillExpire` callback 30 seconds before the token expires. After receiving the callback, you obtain a new token from the server and call `renewToken` to pass the newly generated token to the SDK.

    * If the token expires, the SDK triggers an `onRequestToken` callback. After receiving the callback, obtain a new token from the server and call `joinChannel` with the new token to rejoin the channel.

    Build and run the project on the local device, the app performs the following operations:

    * Obtains a token from your token server.
    * Joins the channel.
    * Automatically renews the token when it is about to expire.

    <CalloutContainer type="info">
      <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-5]

    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-4]

    * [renewToken](https://api-ref.agora.io/en/voice-sdk/electron/4.x/API/class_irtcengine.html#api_irtcengine_renewtoken)
    * [onTokenPrivilegeWillExpire](https://api-ref.agora.io/en/voice-sdk/electron/4.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_ontokenprivilegewillexpire)
    * [onRequestToken](https://api-ref.agora.io/en/voice-sdk/electron/4.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_onrequesttoken)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="flutter">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="flutter" />

    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 Voice Calling:

    **Token authentication flow**

    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)

    ## Prerequisites [#prerequisites-6]

    Before starting, ensure that you have:

    * Implemented the [Quickstart](../index.mdx) in your project.

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

      * [Deploy a token server](deploy-token-server.mdx)
      * [Deploy a middleware server](middleware-token-server.md)

    ## Implement basic authentication [#implement-basic-authentication-6]

    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-6]

    This section shows you how to integrate token authentication in your app.

    1. Add the following to the `pubspec.yaml` file, under `dependencies`:

       ```yaml
       dependencies:
         # Agora Flutter SDK, use the latest version of agora_rtc_engine
         agora_rtc_engine: ^6.3.0
         # For making http requests
         http: ^0.13.5
       ```

    2. Replace the contents in `/lib/main.dart` with the following code.

       **Sample code for basic authentication**

       ```dart
         import 'dart:convert';

           import 'package:agora_rtc_engine/agora_rtc_engine.dart';
           import 'package:flutter/material.dart';
           import 'package:http/http.dart' as http;

           void main() => runApp(const MyApp());

           /// This widget is the root of your application.
           class MyApp extends StatefulWidget {
           /// Construct the [MyApp]
           const MyApp({Key? key}) : super(key: key);

           @override
           State<MyApp> createState() => _MyAppState();
           }

           class _MyAppState extends State<MyApp> {

           @override
           Widget build(BuildContext context) {
             return MaterialApp(
             theme: ThemeData(
               primarySwatch: Colors.blue,
             ),
             home: Scaffold(
               appBar: AppBar(
                 title: const Text('APIExample'),
               ),
               body: const JoinChannelVideoToken()),
             );
           }
           }

           class JoinChannelVideoToken extends StatefulWidget {
           const JoinChannelVideoToken({Key? key}) : super(key: key);

           @override
           State<StatefulWidget> createState() => _State();
           }

           class _State extends State<JoinChannelVideoToken> {
           late final RtcEngine _engine;
           bool _isReadyPreview = false;

           bool isJoined = false, switchCamera = true, switchRender = true;
           Set<int> remoteUid = {};
           static const String appId = '<Your app ID>'; // Fill in the information from Agora console
           static const String channelId = '<Your channel name>'; // Fill in the channel name
           static const String hostUrl = '<Your host URL and port>'; // Fill in the server URL and port

           @override
           void initState() {
             super.initState();
             _initEngine();
           }

           @override
           void dispose() {
             super.dispose();
             _dispose();
           }

           Future<void> _dispose() async {
             await _engine.leaveChannel();
             await _engine.release();
           }

           Future<void> _initEngine() async {
             _engine = createAgoraRtcEngine();
             await _engine.initialize(const RtcEngineContext(
             appId: appId,
             ));

             _engine.registerEventHandler(RtcEngineEventHandler(
             onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
               setState(() {
               isJoined = true;
               });
             },
             onUserJoined: (RtcConnection connection, int rUid, int elapsed) {
               setState(() {
               remoteUid.add(rUid);
               });
             },
             onUserOffline:
               (RtcConnection connection, int rUid, UserOfflineReasonType reason) {
               setState(() {
               remoteUid.removeWhere((element) => element == rUid);
               });
             },
             onLeaveChannel: (RtcConnection connection, RtcStats stats) {
               setState(() {
               isJoined = false;
               remoteUid.clear();
               });
             },
             onTokenPrivilegeWillExpire: (RtcConnection connection, String token) {
               _fetchToken(1234, channelId, 1, false);
             },
             onRequestToken: (RtcConnection connection) {
               _fetchToken(1234, channelId, 1, true);
             },
             ));

             await _engine.enableVideo();

             await _engine.startPreview();
             await _fetchToken(1234, channelId, 1, true);

             setState(() {
             _isReadyPreview = true;
             });
           }

           Future<void> _fetchToken(
             int uid,
             String channelName,
             int toeknRole,
             bool needJoinChannel,
           ) async {
             var client = http.Client();
             try {
             Map<String, String> headers = {
               'Content-type': 'application/json',
               'Accept': 'application/json',
             };

             var response = await client.post(Uri.parse(hostUrl),
               headers: headers,
               body: jsonEncode(
                 {'uid': uid, 'ChannelName': channelName, 'role': toeknRole}));
             var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map;

             final token = decodedResponse['token'];
             if (needJoinChannel) {
               await _engine.joinChannel(
               token: token,
               channelId: channelName,
               uid: uid,
               options: const ChannelMediaOptions(
                 channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
                 clientRoleType: ClientRoleType.clientRoleBroadcaster,
               ),
               );
             } else {
               await _engine.renewToken(token);
             }
             } finally {
             client.close();
             }
           }

           @override
           Widget build(BuildContext context) {
             if (!_isReadyPreview) return Container();
             return Stack(
             children: [
               AgoraVideoView(
               controller: VideoViewController(
                 rtcEngine: _engine,
                 canvas: const VideoCanvas(uid: 0),
               ),
               ),
               Align(
               alignment: Alignment.topLeft,
               child: SingleChildScrollView(
                 scrollDirection: Axis.horizontal,
                 child: Row(
                 children: List.of(remoteUid.map(
                   (e) => SizedBox(
                   width: 120,
                   height: 120,
                   child: AgoraVideoView(
                     controller: VideoViewController.remote(
                     rtcEngine: _engine,
                     canvas: VideoCanvas(uid: e),
                     connection: const RtcConnection(channelId: channelId),
                     ),
                   ),
                   ),
                 )),
                 ),
               ),
               )
             ],
             );
           }
           }
       ```

       Replace `<Your app ID>` with your app ID, which must be consistent with the app ID you specified in the server configuration. Update `<Your host URL and port>` with the host URL and port of the local Golang server you have deployed. For example `99.9.9.99:8082`.

    The sample code implements the following logic:

    * Calls `joinChannel` to join a channel using the user ID, the channel name, and a token you obtain from the server. The user ID and channel name you specify must be consistent with the values you used to generate the token.

    * The SDK triggers an `onTokenPrivilegeWillExpire` callback 30 seconds before the token expires. After receiving the callback, you obtain a new token from the server and call `renewToken` to pass the newly generated token to the SDK.

    * If the token expires, the SDK triggers an `onRequestToken` callback. After receiving the callback, obtain a new token from the server and call `joinChannel` with the new token to rejoin the channel.

    Build and run the project on the local device, the app performs the following operations:

    * Obtains a token from your token server.
    * Joins the channel.
    * Automatically renews the token when it is about to expire.

    <CalloutContainer type="info">
      <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-6]

    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-5]

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

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

    * [onConnectionStateChanged](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_onconnectionstatechanged)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="react-native">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="react-native" />

    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 Voice Calling:

    **Token authentication flow**

    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)

    ## Prerequisites [#prerequisites-7]

    Before starting, ensure that you have:

    * Implemented the [Quickstart](../index.mdx) in your project.

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

      * [Deploy a token server](deploy-token-server.mdx)
      * [Deploy a middleware server](middleware-token-server.md)

    ## Implement basic authentication [#implement-basic-authentication-7]

    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-7]

    This section shows you how to integrate token authentication in your app.

    1. Open the [SDK quickstart](../index.mdx) project you created earlier.

    2. In `ProjectName.tsx` replace the contents with the following:

       **Sample code for basic authentication**

       ```js
         import React, { useCallback, useEffect, useRef, useState } from 'react';
           import {
             PermissionsAndroid,
             Platform,
             SafeAreaView,
             ScrollView,
             StyleSheet,
             Switch,
             Text,
             View,
           } from 'react-native';

           import {
             ChannelProfileType,
             ClientRoleType,
             createAgoraRtcEngine,
             IRtcEngine,
             IRtcEngineEventHandler,
             RtcConnection,
             RtcSurfaceView,
           } from 'react-native-agora';

           // Define basic information
           const appId = '<Your app ID>';
           const channelName = 'test';
           const uid = 123; // Local user Uid

           const App = () => {
           const agoraEngineRef = useRef<IRtcEngine>(); // IRtcEngine instance
           const eventHandlerRef = useRef<IRtcEngineEventHandler>(); // IRtcEngine event handler
           const [token, setToken] = useState<string>(''); // RTC Token
           const [isJoined, setIsJoined] = useState(false); // Whether the local user has joined the channel
           const [isHost, setIsHost] = useState(true); // User role
           const [remoteUid, setRemoteUid] = useState(0); // Uid of remote user
           const [message, setMessage] = useState(''); // Message to the user

           // Send a request to obtain a token to the token server
           const fetchToken = useCallback(() => {
             // Token server URL example: http://12.123.1.123:8082/fetch_rtc_token
             const url = '<Your host URL and port>/fetch_rtc_token';
             const body = JSON.stringify({
             uid,
             ChannelName: channelName,
             role: isHost
               ? ClientRoleType.ClientRoleBroadcaster
               : ClientRoleType.ClientRoleAudience,
             });
             console.log('fetchToken', url, body);
             return fetch(url, {
               method: 'POST',
               body,
             })
             .then((res) => res.json())
             .then((res) => {
               showMessage('token ' + res.token + 'Already refreshed');
               if (+res.code === 200) {
               setToken(res.token);
               }
               return res;
             });
             }, [isHost]);

           // Use the obtained token to join the channel
           const join = useCallback(async () => {
             if (!token) {
               await fetchToken();
             } else {
             try {
               // After joining the channel, set the channel profile to live broadcast
               agoraEngineRef.current?.setChannelProfile(
               ChannelProfileType.ChannelProfileLiveBroadcasting
               );
               if (isHost) {
               agoraEngineRef.current?.startPreview();
               // Join the channel as a host
               agoraEngineRef.current?.joinChannel(token, channelName, uid, {
                 publishCameraTrack: true,
                 clientRoleType: ClientRoleType.ClientRoleBroadcaster,
               });
               } else {
               // Join the channel as audience
               agoraEngineRef.current?.joinChannel(token, channelName, uid, {
                 clientRoleType: ClientRoleType.ClientRoleAudience,
               });
               }
             } catch (e) {
               console.log(e);
             }
           }
           }, [fetchToken, isHost, token]);

           const leave = () => {
             try {
               agoraEngineRef.current?.leaveChannel();
               setRemoteUid(0);
               setIsJoined(false);
               showMessage('Left the channel');
             } catch (e) {
               console.error(e);
             }
           };

           useEffect(() => {
             const setupVideoSDKEngine = async () => {
             try {
               if (Platform.OS === 'android') {
               await getPermission();
               }
               agoraEngineRef.current = createAgoraRtcEngine();
               eventHandlerRef.current = {
               onJoinChannelSuccess: () => {
                 showMessage('Successfully joined the channel: ' + channelName);
                 setIsJoined(true);
               },
               onUserJoined: (_connection, Uid) => {
                 showMessage('Remote user ' + Uid + ' joined');
                 setRemoteUid(Uid);
               },
               onUserOffline: (_connection, Uid) => {
                 showMessage('Remote user ' + Uid + ' left the channel');
                 setRemoteUid(0);
               },
               onTokenPrivilegeWillExpire(connection: RtcConnection, token: string) {
                 showMessage('token ' + token + 'Expires soon');
                 fetchToken();
               },
               onRequestToken(connection: RtcConnection) {
                 showMessage('token expired');
                 setToken('');
               },
               };
               const agoraEngine = agoraEngineRef.current;
               //Initialize engine
               agoraEngine.initialize({
                 appId: appId,
                 channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
               });
               // Register event handler
               agoraEngine.registerEventHandler(eventHandlerRef.current);
               // Start local video
               agoraEngine.enableVideo();
             } catch (e) {
               console.error(e);
             }
           };

             setupVideoSDKEngine();

             return () => {
               agoraEngineRef.current?.release();
             };
           }, []); // eslint-disable-line react-hooks/exhaustive-deps

           useEffect(() => {
             const checkJoined = () => {
             if (isJoined) {
               console.log('renewToken');
               // agoraEngineRef.current?.renewToken(token);
             } else {
               console.log('joinChannel');
               join();
             }
             };
             checkJoined();
           }, [token]); // eslint-disable-line react-hooks/exhaustive-deps

           // Render the user interface
           return (
             <SafeAreaView style={styles.main}>
             <Text style={styles.head}>Agora SDK Quickstart</Text>
             <View style={styles.btnContainer}>
               <Text onPress={join} style={styles.button}>
               Join channel
               </Text>
               <Text onPress={leave} style={styles.button}>
               Leave Channel
               </Text>
             </View>
             <View style={styles.btnContainer}>
               <Text>Audience</Text>
               <Switch
               onValueChange={(switchValue) => {
                 setIsHost(switchValue);
                 if (isJoined) {
                 leave();
                 }
               }}
               value={isHost}
               />
               <Text>Host</Text>
             </View>
             <ScrollView
               style={styles.scroll}
               contentContainerStyle={styles.scrollContainer}
             >
               {isJoined ? (
               <React.Fragment key={0}>
                 <RtcSurfaceView canvas={{ uid: 0 }} style={styles.videoView} />
                 <Text>Local user uid: {uid}</Text>
               </React.Fragment>
               ) : (
               <Text>Join a channel</Text>
               )}
               {isJoined && !isHost && remoteUid !== 0 ? (
               <React.Fragment key={remoteUid}>
                 <RtcSurfaceView
                 canvas={{ uid: remoteUid }}
                 style={styles.videoView}
                 />
                 <Text>Remote user uid: {remoteUid}</Text>
               </React.Fragment>
               ) : (
               <Text>{isJoined && !isHost ? 'Wait for a remote user to join' : ''}</Text>
               )}
               <Text style={styles.info}>{message}</Text>
             </ScrollView>
             </SafeAreaView>
           );

           // Show message
           function showMessage(msg: string) {
             console.info(msg);
             setMessage(msg);
           }
           };

           // Define user interface style
           const styles = StyleSheet.create({
             button: {
               paddingHorizontal: 25,
               paddingVertical: 4,
               fontWeight: 'bold',
               color: '#ffffff',
               backgroundColor: '#0055cc',
               margin: 5,
             },
             main: { flex: 1, alignItems: 'center' },
             scroll: { flex: 1, backgroundColor: '#ddeeff', width: '100%' },
             scrollContainer: { alignItems: 'center' },
             videoView: { width: '90%', height: 200 },
             btnContainer: { flexDirection: 'row', justifyContent: 'center' },
             head: { fontSize: 20 },
             info: { backgroundColor: '#ffffe0', paddingHorizontal: 8, color: '#0000ff' },
           });

           const getPermission = async () => {
             if (Platform.OS === 'android') {
               await PermissionsAndroid.requestMultiple([
               'android.permission.RECORD_AUDIO',
               'android.permission.CAMERA',
               ]);
             }
           };

           export default App;
       ```

       Replace `<Your app ID>` with your app ID, which must be consistent with the app ID you specified in the server configuration. Update `<Your host URL and port>` with the host URL and port of the local Golang server you have deployed. For example `99.9.9.99:8082`.

    The sample code implements the following logic:

    * Calls `joinChannel` to join a channel using the user ID, the channel name, and a token you obtain from the server. The user ID and channel name you specify must be consistent with the values you used to generate the token.

    * The SDK triggers an `onTokenPrivilegeWillExpire` callback 30 seconds before the token expires. After receiving the callback, you obtain a new token from the server and call `renewToken` to pass the newly generated token to the SDK.

    * If the token expires, the SDK triggers an `onRequestToken` callback. After receiving the callback, obtain a new token from the server and call `joinChannel` with the new token to rejoin the channel.

    Build and run the project on the local device, the app performs the following operations:

    * Obtains a token from your token server.
    * Joins the channel.
    * Automatically renews the token when it is about to expire.

    <CalloutContainer type="info">
      <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-7]

    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-6]

    * [renewToken](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_irtcengine_renewtoken)
    * [onTokenPrivilegeWillExpire](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_ontokenprivilegewillexpire)
    * [onRequestToken](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengineeventhandler.html#callback_irtcengineeventhandler_onrequesttoken)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="javascript">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="javascript" />

    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 Voice Calling:

    **Token authentication flow**

    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)

    ## Prerequisites [#prerequisites-8]

    Before starting, ensure that you have:

    * Implemented the [Quickstart](../index.mdx) in your project.

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

      * [Deploy a token server](deploy-token-server.mdx)
      * [Deploy a middleware server](middleware-token-server.md)

    ## Implement basic authentication [#implement-basic-authentication-8]

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

    ### Use a token [#use-a-token]

    1. **Import the components and hooks you need to handle the authentication workflow**:

       ```typescript
       import { useClientEvent, useRTCClient } from "agora-rtc-react";
       ```

    2. **Retrieve a token from the authentication server**:

       ```typescript
       async function fetchRTCToken(channelName: string) {
        if (config.serverUrl !== "") {
         try {
          const response = await fetch(
           `${config.proxyUrl}${config.serverUrl}/rtc/${channelName}/publisher/uid/${config.uid}/?expiry=${config.tokenExpiryTime}`
          );
          const data = await response.json();
          console.log("RTC token fetched from server: ", data.rtcToken);
          return data.rtcToken;
         } catch (error) {
          console.error(error);
          throw error;
         }
        } else {
         return config.rtcToken;
        }
       }
       ```

    3. **Handle the event triggered by Agora SDRTN® when the token is about to expire**:

       A token expires after the `tokenExpiryTime` specified in the call to the token server or after 24 hours, if the time is not specified. The `useTokenWillExpire` method receives a callback when the current token is about to expire so that a fresh token may be retrieved and used.

       ```typescript
       const useTokenWillExpire = () => {
        const agoraEngine = useRTCClient();
        useClientEvent(agoraEngine, "token-privilege-will-expire", () => {
         if (config.serverUrl !== "") {
          fetchRTCToken(config.channelName)
           .then((token: string) => {
            console.log("RTC token fetched from server: ", token);
            return agoraEngine.renewToken(token);
           })
           .catch((error) => {
            console.error(error);
           });
         } else {
          console.log("Please make sure you specified the token server URL in the configuration file");
         }
        });
       };
       ```

    <CalloutContainer type="info">
      <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-8]

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

    * [Reference app](https://github.com/AgoraIO/video-sdk-samples-reactjs/tree/main#samples)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="unity">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unity" />

    When a user attempts to connect to an Agora channel, your game retrieves a token from the token server in your security infrastructure. Your game 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 Voice Calling:

    **Token authentication flow**

    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)

    ## Prerequisites [#prerequisites-9]

    Before starting, ensure that you have:

    * Implemented the [Quickstart](../index.mdx) in your project.

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

      * [Deploy a token server](deploy-token-server.mdx)
      * [Deploy a middleware server](middleware-token-server.md)

    ## Implement basic authentication [#implement-basic-authentication-9]

    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-8]

    This section shows you how to integrate token authentication in your game.

    1. Open the [SDK quickstart](../index.mdx) project you created earlier.

    2. In `joinChannelVideo.cs` replace the contents with the following:

       **Sample code for basic authentication**

       ```cpp
          using UnityEngine;
           using UnityEngine.UI;
           using UnityEngine.Serialization;
           using Agora.Rtc;
           using Agora.Util;
           using Logger = Agora.Util.Logger;

           namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.JoinChannelVideoToken
           {
             public class JoinChannelVideoToken : MonoBehaviour
             {
               // Fill in your App ID and channel name
               public string appID = "<Your App ID>";
               public string channelName = "test";
               public uint localUid = 0;
               internal static string channelToken = "";
               internal IRtcEngine RtcEngine = null;

               internal CONNECTION_STATE_TYPE _state = CONNECTION_STATE_TYPE.CONNECTION_STATE_DISCONNECTED;

               private void Start()
               {

                 if (CheckAppId())
                 {
                   InitEngine();
                   JoinChannel();
                 }
               }

               internal void RenewOrJoinToken(TokenObject tokenObject)
               {
                 if (tokenObject == null || tokenObject.code != 200) {
                   Debug.Log("get token failed");
                   return;
                 }

                 JoinChannelVideoToken.channelToken = tokenObject.token;
                 if (_state == CONNECTION_STATE_TYPE.CONNECTION_STATE_DISCONNECTED
                   || _state == CONNECTION_STATE_TYPE.CONNECTION_STATE_DISCONNECTED
                   || _state == CONNECTION_STATE_TYPE.CONNECTION_STATE_FAILED
                 )
                 {
                   // Use the token to join the channel
                   JoinChannel();
                 }
                 else
                 {
                   // Update token in the channel
                   UpdateToken();
                 }
               }

               private void Update()
               {
                 PermissionHelper.RequestMicrophonePermission();
                 PermissionHelper.RequestCameraPermission();
               }

               private void UpdateToken()
               {
                 RtcEngine.RenewToken(JoinChannelVideoToken.channelToken);
                 Debug.Log("renewToken: " + JoinChannelVideoToken.channelToken);
               }

               private bool CheckAppId()
               {
                 if (appID.Length < 10) {
                   Debug.Log("Please fill in your appId in Inspector");
                   return false;
                 }
                 return true;
               }

               // Initialization
               private void InitEngine()
               {
                 RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
                 UserEventHandler handler = new UserEventHandler(this);
                 RtcEngineContext context = new RtcEngineContext(appID, 0,
                   CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING, null,
                   AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
                 RtcEngine.Initialize(context);
                 RtcEngine.InitEventHandler(handler);
               }
               // Join channel
               private void JoinChannel()
               {
                 RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
                 RtcEngine.EnableAudio();
                 RtcEngine.EnableVideo();

                 if (channelToken.Length == 0)
                 {
                   StartCoroutine(HelperClass.FetchToken(this.localUid, channelName,CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER, this.RenewOrJoinToken));
                   return;
                 }

                 RtcEngine.JoinChannel(channelToken, channelName, "");
                 Debug.Log("joinChannel with token:" + channelToken);
               }

               internal void StartUpdateTokenEveryTenSecond() {
                 InvokeRepeating("RequestToken", 10, 10);
               }

               private void RequestToken() {
                 StartCoroutine(HelperClass.FetchToken(this.localUid, channelName, CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER, this.RenewOrJoinToken));
               }

               private void OnDestroy()
               {
                 Debug.Log("OnDestroy");
                 if (RtcEngine == null) return;
                 RtcEngine.InitEventHandler(null);
                 RtcEngine.LeaveChannel();
                 RtcEngine.Dispose();
               }

               internal string GetChannelName()
               {
                 return channelName;
               }

               #region -- Video Render UI Logic ---

               internal static void MakeVideoView(uint uid, string channelId = "")
               {
                 GameObject go = GameObject.Find(uid.ToString());
                 if (!ReferenceEquals(go, null))
                 {
                   return;
                 }

                 VideoSurface videoSurface = MakeImageSurface(uid.ToString());
                 if (!ReferenceEquals(videoSurface, null))
                 {

                   if (uid == 0)
                   {
                     videoSurface.SetForUser(uid, channelId);
                   }
                   else
                   {
                     videoSurface.SetForUser(uid, channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
                   }

                   videoSurface.OnTextureSizeModify += (int width, int height) =>
                   {
                     float scale = (float)height / (float)width;
                     videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
                     Debug.Log("OnTextureSizeModify: " + width + " " + height);
                   };

                   videoSurface.SetEnable(true);
                 }
               }

               private static VideoSurface MakePlaneSurface(string goName)
               {
                 GameObject go = GameObject.CreatePrimitive(PrimitiveType.Plane);

                 if (go == null)
                 {
                   return null;
                 }

                 go.name = goName;

                 go.transform.Rotate(-90.0f, 0.0f, 0.0f);
                 go.transform.position = Vector3.zero;
                 go.transform.localScale = new Vector3(0.25f, 0.5f, .5f);

                 var videoSurface = go.AddComponent<VideoSurface>();
                 return videoSurface;
               }

               private static VideoSurface MakeImageSurface(string goName)
               {
                 GameObject go = new GameObject();

                 if (go == null)
                 {
                   return null;
                 }

                 go.name = goName;
                 go.AddComponent<RawImage>();
                 go.AddComponent<UIElementDrag>();
                 GameObject canvas = GameObject.Find("VideoCanvas");
                 if (canvas != null)
                 {
                   go.transform.parent = canvas.transform;
                   Debug.Log("add video view");
                 }
                 else
                 {
                   Debug.Log("Canvas is null video view");
                 }

                 go.transform.Rotate(0f, 0.0f, 180.0f);
                 go.transform.localPosition = Vector3.zero;
                 go.transform.localScale = new Vector3(3f, 4f, 1f);

                 var videoSurface = go.AddComponent<VideoSurface>();
                 return videoSurface;
               }

               internal static void DestroyVideoView(uint uid)
               {
                 GameObject go = GameObject.Find(uid.ToString());
                 if (!ReferenceEquals(go, null))
                 {
                   Object.Destroy(go);
                 }
               }

               #endregion
             }

             internal class UserEventHandler : IRtcEngineEventHandler
             {
               private readonly JoinChannelVideoToken _helloVideoTokenAgora;

               internal UserEventHandler(JoinChannelVideoToken helloVideoTokenAgora)
               {
                 _helloVideoTokenAgora= helloVideoTokenAgora;
               }

               public override void OnError(int err, string msg)
               {
                 Debug.Log(string.Format("OnError err: {0}, msg: {1}", err, msg));
               }
               // Joined the channel successfully
               public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
               {
                 int build = 0;
                 Debug.Log(string.Format("sdk version: ${0}",
                   _helloVideoTokenAgora.RtcEngine.GetVersion(ref build)));
                 Debug.Log(
                   string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
                     connection.channelId, connection.localUid, elapsed));
                 Debug.Log(string.Format("New Token: {0}",
                   JoinChannelVideoToken.channelToken));
                 JoinChannelVideoToken.MakeVideoView(0);

                 _helloVideoTokenAgora.localUid = connection.localUid;
                 _helloVideoTokenAgora.StartUpdateTokenEveryTenSecond();
               }

               public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
               {
                 Debug.Log(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid,
                   elapsed));
                 JoinChannelVideoToken.MakeVideoView(uid, _helloVideoTokenAgora.GetChannelName());
               }

               public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
               {
                 Debug.Log(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
                   (int)reason));
                 JoinChannelVideoToken.DestroyVideoView(uid);
               }
               //The token is about to expire
               public override void OnTokenPrivilegeWillExpire(RtcConnection connection, string token)
               {
                 _helloVideoTokenAgora.StartCoroutine(HelperClass.FetchToken(_helloVideoTokenAgora.localUid,
                   _helloVideoTokenAgora.GetChannelName(), CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER, _helloVideoTokenAgora.RenewOrJoinToken));
               }

               public override void OnConnectionStateChanged(RtcConnection connection, CONNECTION_STATE_TYPE state,
                 CONNECTION_CHANGED_REASON_TYPE reason)
               {
                 _helloVideoTokenAgora._state = state;
               }

               public override void OnConnectionLost(RtcConnection connection)
               {
                 Debug.Log(string.Format("OnConnectionLost "));
               }
             }

             #endregion
           }

           namespace Agora.Util
           {
             public static class HelperClass
             {
               public static IEnumerator FetchToken(uint uid, string channelName, CLIENT_ROLE_TYPE role, Action<TokenObject> callback = null)
               {

                 Dictionary<string, object> postParams = new Dictionary<string, object>();
                 postParams.Add("uid", uid);
                 postParams.Add("ChannelName", channelName);
                 postParams.Add("role", role);
                 string json = AgoraJson.ToJson<Dictionary<string, object>>(postParams);
                 var url = "http://127.0.0.1/fetch_rtc_token";

                 byte[] postBytes = System.Text.Encoding.Default.GetBytes(json);
                 UnityWebRequest request = new UnityWebRequest(url, "POST");

                 request.uploadHandler = (UploadHandler)new UploadHandlerRaw(postBytes);
                 request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
                 request.SetRequestHeader("Content-Type", "application/json");
                 yield return request.SendWebRequest();

                 if (request.isNetworkError || request.isHttpError)
                 {
                   Debug.Log(request.error);
                   callback(null);
                   yield break;
                 }
                 Debug.Log("text: " + request.downloadHandler.text);
                 TokenObject tokenInfo = JsonUtility.FromJson<TokenObject>(request.downloadHandler.text);
                 callback(tokenInfo);
               }
             }
           }
       ```

       Replace `<Your app ID>` with your app ID, which must be consistent with the app ID you specified in the server configuration. Update `url` with the host URL and port of the local Golang server you have deployed.

    The sample code implements the following logic:

    * Calls `JoinChannel` to join a channel using the user ID, the channel name, and a token you obtain from the server. The user ID and channel name you specify must be consistent with the values you used to generate the token.

    * The SDK triggers an `OnTokenPrivilegeWillExpire` callback 30 seconds before the token expires. After receiving the callback, you obtain a new token from the server and call `RenewToken` to pass the newly generated token to the SDK.

    Build and run the project on the local device, the game performs the following operations:

    * Obtains a token from your token server.
    * Joins the channel.
    * Automatically renews the token when it is about to expire.

    <CalloutContainer type="info">
      <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-9]

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

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="unreal">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unreal" />

    When a user attempts to connect to an Agora channel, your game retrieves a token from the token server in your security infrastructure. Your game 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 Voice Calling:

    **Token authentication flow**

    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)

    ## Prerequisites [#prerequisites-10]

    Before starting, ensure that you have:

    * Implemented the [Quickstart](../index.mdx) in your project.

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

      * [Deploy a token server](deploy-token-server.mdx)
      * [Deploy a middleware server](middleware-token-server.md)

    ## Implement basic authentication [#implement-basic-authentication-10]

    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-9]

    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.

    ```cpp
    // Channel name
    const char* channelId;
    // User ID
    int uid = 0
    // Request a token from the server corresponding to channelId and uid
    const char* token = getToken();
    // Set channel media options
    ChannelMediaOptions options;
    // Set the user role as host
    options.clientRoleType = CLIENT_ROLE_TYPE::CLIENT_ROLE_BROADCASTER;
    // Join the channel
    m_rtcEngine->joinChannel(token, channelId, uid, options);
    ```

    ### Token expiration [#token-expiration-5]

    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-4]

    * 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-4]

    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.

    ```cpp
    class CJoinChannelVideoByTokenRtcEngineEventHandler
      : public IRtcEngineEventHandler
    {
    public:
      // Triggered when a token is about to expire
      virtual void onTokenPrivilegeWillExpire(const char* token) {
      // Request to generate a fresh token
      const char* token = getToken();
      // Renew token
      m_rtcEngine->renewToken(token);
      }
    }
    ```

    <CalloutContainer type="info">
      <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-10]

    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-7]

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

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

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="blueprint">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="blueprint" />

    When a user attempts to connect to an Agora channel, your game retrieves a token from the token server in your security infrastructure. Your game 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 Voice Calling:

    **Token authentication flow**

    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)

    ## Prerequisites [#prerequisites-11]

    Before starting, ensure that you have:

    * Implemented the [Quickstart](../index.mdx) in your project.

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

      * [Deploy a token server](deploy-token-server.mdx)
      * [Deploy a middleware server](middleware-token-server.md)

    ## Implement basic authentication [#implement-basic-authentication-11]

    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-10]

    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.

    ```cpp
    // Channel name
    const char* channelId;
    // User ID
    int uid = 0
    // Request a token from the server corresponding to channelId and uid
    const char* token = getToken();
    // Set channel media options
    ChannelMediaOptions options;
    // Set the user role as host
    options.clientRoleType = CLIENT_ROLE_TYPE::CLIENT_ROLE_BROADCASTER;
    // Join the channel
    m_rtcEngine->joinChannel(token, channelId, uid, options);
    ```

    ### Token expiration [#token-expiration-6]

    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-5]

    * 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-5]

    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.

    ```cpp
    class CJoinChannelVideoByTokenRtcEngineEventHandler
      : public IRtcEngineEventHandler
    {
    public:
      // Triggered when a token is about to expire
      virtual void onTokenPrivilegeWillExpire(const char* token) {
      // Request to generate a fresh token
      const char* token = getToken();
      // Renew token
      m_rtcEngine->renewToken(token);
      }
    }
    ```

    <CalloutContainer type="info">
      <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-11]

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

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="python">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="python" />

    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 Voice Calling:

    **Token authentication flow**

    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)

    ## Prerequisites [#prerequisites-12]

    Before starting, ensure that you have:

    * Implemented the [Quickstart](../index.mdx) in your project.

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

      * [Deploy a token server](deploy-token-server.mdx)
      * [Deploy a middleware server](middleware-token-server.md)

    ## Implement basic authentication [#implement-basic-authentication-12]

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

    <CalloutContainer type="info">
      <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-12]

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

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="linux-cpp">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="linux-cpp" />

    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 Voice Calling:

    **Token authentication flow**

    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)

    ## Prerequisites [#prerequisites-13]

    Before starting, ensure that you have:

    * Implemented the [Quickstart](../index.mdx) in your project.

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

      * [Deploy a token server](deploy-token-server.mdx)
      * [Deploy a middleware server](middleware-token-server.md)

    ## Implement basic authentication [#implement-basic-authentication-13]

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

    <CalloutContainer type="info">
      <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-13]

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

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="linux-c">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="linux-c" />

    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 Voice Calling:

    **Token authentication flow**

    ![token authentication flow](https://assets-docs.agora.io/images/video-sdk/token-authentication.svg)

    ## Prerequisites [#prerequisites-14]

    Before starting, ensure that you have:

    * Implemented the [Quickstart](../index.mdx) in your project.

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

      * [Deploy a token server](deploy-token-server.mdx)
      * [Deploy a middleware server](middleware-token-server.md)

    ## Implement basic authentication [#implement-basic-authentication-14]

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

    <CalloutContainer type="info">
      <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-14]

    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-8]

    * [agora\_rtc\_renew\_token](https://api-ref.agora.io/en/iot-sdk/linux/1.x/agora__rtc__api_8h.html#a87c94a2d518278926807e24592b41336)

    * [on\_token\_privilege\_will\_expire](https://api-ref.agora.io/en/iot-sdk/linux/1.x/structagora__rtc__event__handler__t.html#aef1aa16dd5b32baa1aa03887f40f6ee9)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>
</_PlatformTabsGroup>
