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

> 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

      
  
      
  
      
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

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

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.

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

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.

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

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

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

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

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

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

    
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
      
  
