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

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

Note

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.