Use tokens

Updated

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

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

Understand the tech

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

The following figure shows the call flow you implement to create step-up-authentication with Agora Voice Calling:

Token authentication flow

Prerequisites

Before starting, ensure that you have:

Implement basic authentication

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

Use a token to join a channel

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

// Channel name
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

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

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

Single channel use-case

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

  • Call 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

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.

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

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

.

Complete sample code for token authentication

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)")
        }
    }
  }
}

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

Reference

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

API reference