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

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

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

## Understand the tech [#understand-the-tech]

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

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

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

## Prerequisites [#prerequisites]

Before starting, ensure that you have:

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

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

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

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

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

      
  
      
  
      
  
      
    ### Use a token to join a channel [#use-a-token-to-join-a-channel-3]

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

    ```swift
        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="warning">
  <CalloutTitle>
    Note
  </CalloutTitle>

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

## Reference [#reference]

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

      
  
      
  
      
    ### API reference [#api-reference-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:\))

    
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
