Skip to main content

Secure authentication with tokens

Authentication is the act of validating the identity of each user before they access your system. Agora uses digital tokens to authenticate users and their privileges before they access an Agora service, such as joining an Agora call, or logging into the Signaling system.

This document shows you how to create a Signaling token server and a Signaling client app. The Signaling client app retrieves a Signaling token from the Signaling token server. This token authenticates the current user when the user accesses Signaling.

Understand the tech

The following figure shows the steps in the authentication flow:

 token authentication flow

a Signaling token is a dynamic key generated on your app server that is valid for 24 hours. When your users log in to Signaling from your app client, Signaling validates the token and reads the user and project information stored in the token. a Signaling token contains the following information:

  • The App ID of your Agora project

  • The App Certificate of your Agora project

  • The Signaling user ID of the user to be authenticated

  • The Unix timestamp when the token expires

Prerequisites

In order to follow this procedure you must have the following:

Implement the authentication flow

This section shows you how to supply and consume a token that gives rights to specific functionality to authenticated users using the source code provided by Agora.

Get the App ID and App Certificate

This section shows you how to get the security information needed to generate a token, including the App ID and App Certificate of your project.

1. Get the App ID

Agora automatically assigns each project an App ID as a unique identifier.

To copy this App ID, find your project on the Project Management page in Agora Console, and click the plus icon in the App ID column.

2. Get the App Certificate

To get an App Certificate, do the following:

  1. On the Project Management page, click Config for the project you want to use. 1641971710869

  2. Click the copy icon under Primary Certificate. 1637660100222

Deploy a token server

Token generators create the tokens requested by your client app to enable secure access to Agora Platform. To serve these tokens you deploy a generator in your security infrastructure.

In order to show the authentication workflow, this section shows how to build and run a token server written in Golang on your local machine.

This sample server is for demonstration purposes only. Do not use it in a production environment.

  1. Create a file, server.go, with the following content. Then replace <Your App ID> and <Your App Certificate> with your App ID and App Certificate.

    Implement either:

    • AccessToken2


      _102
      package main
      _102
      _102
      import (
      _102
      rtmtokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtmtokenbuilder2"
      _102
      "fmt"
      _102
      "log"
      _102
      "net/http"
      _102
      "time"
      _102
      "encoding/json"
      _102
      "errors"
      _102
      "strconv"
      _102
      )
      _102
      _102
      type rtm_token_struct struct{
      _102
      Uid_rtm string `json:"uid"`
      _102
      }
      _102
      _102
      var rtm_token string
      _102
      var rtm_uid string
      _102
      _102
      func generateRtmToken(rtm_uid string){
      _102
      _102
      appID := "Your_App_ID"
      _102
      appCertificate := "Your_Certificate"
      _102
      expireTimeInSeconds := uint32(3600)
      _102
      currentTimestamp := uint32(time.Now().UTC().Unix())
      _102
      expireTimestamp := currentTimestamp + expireTimeInSeconds
      _102
      _102
      result, err := rtmtokenbuilder.BuildToken(appID, appCertificate, rtm_uid, expireTimestamp)
      _102
      if err != nil {
      _102
      fmt.Println(err)
      _102
      } else {
      _102
      fmt.Printf("Rtm Token: %s\n", result)
      _102
      _102
      rtm_token = result
      _102
      _102
      }
      _102
      }
      _102
      _102
      func rtmTokenHandler(w http.ResponseWriter, r *http.Request){
      _102
      w.Header().Set("Content-Type", "application/json;charset=UTF-8")
      _102
      w.Header().Set("Access-Control-Allow-Origin", "*")
      _102
      w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS");
      _102
      w.Header().Set("Access-Control-Allow-Headers", "*");
      _102
      _102
      if r.Method == "OPTIONS" {
      _102
      w.WriteHeader(http.StatusOK)
      _102
      return
      _102
      }
      _102
      _102
      if r.Method != "POST" && r.Method != "OPTIONS" {
      _102
      http.Error(w, "Unsupported method. Please check.", http.StatusNotFound)
      _102
      return
      _102
      }
      _102
      _102
      var t_rtm_str rtm_token_struct
      _102
      var unmarshalErr *json.UnmarshalTypeError
      _102
      str_decoder := json.NewDecoder(r.Body)
      _102
      rtm_err := str_decoder.Decode(&t_rtm_str)
      _102
      _102
      if (rtm_err == nil) {
      _102
      rtm_uid = t_rtm_str.Uid_rtm
      _102
      }
      _102
      _102
      if (rtm_err != nil) {
      _102
      if errors.As(rtm_err, &unmarshalErr){
      _102
      errorResponse(w, "Bad request. Please check your params.", http.StatusBadRequest)
      _102
      } else {
      _102
      errorResponse(w, "Bad request.", http.StatusBadRequest)
      _102
      }
      _102
      return
      _102
      }
      _102
      _102
      generateRtmToken(rtm_uid)
      _102
      errorResponse(w, rtm_token, http.StatusOK)
      _102
      log.Println(w, r)
      _102
      }
      _102
      _102
      _102
      func errorResponse(w http.ResponseWriter, message string, httpStatusCode int){
      _102
      w.Header().Set("Content-Type", "application/json")
      _102
      w.Header().Set("Access-Control-Allow-Origin", "*")
      _102
      w.WriteHeader(httpStatusCode)
      _102
      resp := make(map[string]string)
      _102
      resp["token"] = message
      _102
      resp["code"] = strconv.Itoa(httpStatusCode)
      _102
      jsonResp, _ := json.Marshal(resp)
      _102
      w.Write(jsonResp)
      _102
      _102
      }
      _102
      _102
      func main(){
      _102
      // Handling routes
      _102
      // <Vg k="MESS" /> token from <Vg k="MESS" /> uid
      _102
      http.HandleFunc("/fetch_rtm_token", rtmTokenHandler)
      _102
      _102
      fmt.Printf("Starting server at port 8082\n")
      _102
      _102
      if err := http.ListenAndServe(":8082", nil); err != nil {
      _102
      log.Fatal(err)
      _102
      }
      _102
      }

    • AccessToken


      _101
      package main
      _101
      _101
      import (
      _101
      rtmtokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/RtmTokenBuilder"
      _101
      "fmt"
      _101
      "log"
      _101
      "net/http"
      _101
      "time"
      _101
      "encoding/json"
      _101
      "errors"
      _101
      "strconv"
      _101
      )
      _101
      _101
      type rtm_token_struct struct{
      _101
      Uid_rtm string `json:"uid"`
      _101
      }
      _101
      _101
      var rtm_token string
      _101
      var rtm_uid string
      _101
      _101
      func generateRtmToken(rtm_uid string){
      _101
      _101
      appID := "Your_App_ID"
      _101
      appCertificate := "Your_Certificate"
      _101
      expireTimeInSeconds := uint32(3600)
      _101
      currentTimestamp := uint32(time.Now().UTC().Unix())
      _101
      expireTimestamp := currentTimestamp + expireTimeInSeconds
      _101
      _101
      result, err := rtmtokenbuilder.BuildToken(appID, appCertificate, rtm_uid, rtmtokenbuilder.RoleRtmUser, expireTimestamp)
      _101
      if err != nil {
      _101
      fmt.Println(err)
      _101
      } else {
      _101
      fmt.Printf("Rtm Token: %s\n", result)
      _101
      _101
      rtm_token = result
      _101
      _101
      }
      _101
      }
      _101
      _101
      func rtmTokenHandler(w http.ResponseWriter, r *http.Request){
      _101
      w.Header().Set("Content-Type", "application/json;charset=UTF-8")
      _101
      w.Header().Set("Access-Control-Allow-Origin", "*")
      _101
      w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS");
      _101
      w.Header().Set("Access-Control-Allow-Headers", "*");
      _101
      _101
      if r.Method == "OPTIONS" {
      _101
      w.WriteHeader(http.StatusOK)
      _101
      return
      _101
      }
      _101
      _101
      if r.Method != "POST" && r.Method != "OPTIONS" {
      _101
      http.Error(w, "Unsupported method. Please check.", http.StatusNotFound)
      _101
      return
      _101
      }
      _101
      _101
      var t_rtm_str rtm_token_struct
      _101
      var unmarshalErr *json.UnmarshalTypeError
      _101
      str_decoder := json.NewDecoder(r.Body)
      _101
      rtm_err := str_decoder.Decode(&t_rtm_str)
      _101
      _101
      if (rtm_err == nil) {
      _101
      rtm_uid = t_rtm_str.Uid_rtm
      _101
      }
      _101
      _101
      if (rtm_err != nil) {
      _101
      if errors.As(rtm_err, &unmarshalErr){
      _101
      errorResponse(w, "Bad request. Please check your params.", http.StatusBadRequest)
      _101
      } else {
      _101
      errorResponse(w, "Bad request.", http.StatusBadRequest)
      _101
      }
      _101
      return
      _101
      }
      _101
      _101
      generateRtmToken(rtm_uid)
      _101
      errorResponse(w, rtm_token, http.StatusOK)
      _101
      log.Println(w, r)
      _101
      }
      _101
      _101
      func errorResponse(w http.ResponseWriter, message string, httpStatusCode int){
      _101
      w.Header().Set("Content-Type", "application/json")
      _101
      w.Header().Set("Access-Control-Allow-Origin", "*")
      _101
      w.WriteHeader(httpStatusCode)
      _101
      resp := make(map[string]string)
      _101
      resp["token"] = message
      _101
      resp["code"] = strconv.Itoa(httpStatusCode)
      _101
      jsonResp, _ := json.Marshal(resp)
      _101
      w.Write(jsonResp)
      _101
      _101
      }
      _101
      _101
      func main(){
      _101
      // Handling routes
      _101
      // Signaling token from Signaling uid
      _101
      http.HandleFunc("/fetch_rtm_token", rtmTokenHandler)
      _101
      _101
      fmt.Printf("Starting server at port 8082\n")
      _101
      _101
      if err := http.ListenAndServe(":8082", nil); err != nil {
      _101
      log.Fatal(err)
      _101
      }
      _101
      }

  2. A go.mod file defines this module’s import path and dependency requirements. To create the go.mod for your token server, run the following command:


    _1
    $ go mod init sampleServer

  3. Get dependencies by running the following command:


    _1
    $ go get

  4. Start the server by running the following command:


    _1
    $ go run server.go

Use tokens for user authentication

This section uses the Web client as an example to show how to use a token for client-side user authentication.

In order to show the authentication workflow, this section shows how to build and run a Web client on your local machine.

This sample client is for demonstration purposes only. Do not use it in a production environment.

  1. Create the project structure of the Web client with a folder including the following files.

    • index.html: User interface

    • client.js: App logic with Agora Signaling SDK

  2. Download Agora Signaling SDK for Web. Save the JS file in libs to your project directory.

  3. In index.html, add the following code to include the app logic in the UI, then replace <path to the JS file> with the path of the JS file you saved in step 2.


    _11
    <html>
    _11
    <head>
    _11
    <title>Signaling token demo</title>
    _11
    </head>
    _11
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    _11
    <body>
    _11
    <h1>Token demo</h1>
    _11
    <script src="<path to the JS file>"></script>
    _11
    <script src="./client.js"></script>
    _11
    </body>
    _11
    </html>

  4. Create the app logic by editing client.js with the following content. Then replace <Your App ID> with your App ID. The App ID must match the one in the server. You also need to replace <Your Host URL and port> with the host URL and port of the local Golang server you have just deployed, such as 10.53.3.234:8082.


    _71
    // Parameters for the login method
    _71
    let options = {
    _71
    token: "",
    _71
    uid: ""
    _71
    }
    _71
    _71
    // Whether to stop the token renew loop
    _71
    let stopped = false
    _71
    _71
    function sleep (time) {
    _71
    return new Promise((resolve) => setTimeout(resolve, time));
    _71
    }
    _71
    _71
    function fetchToken(uid) {
    _71
    _71
    return new Promise(function (resolve) {
    _71
    axios.post('http://<Your Host URL and port>/fetch_rtm_token', {
    _71
    uid: uid,
    _71
    }, {
    _71
    headers: {
    _71
    'Content-Type': 'application/json; charset=UTF-8'
    _71
    }
    _71
    })
    _71
    .then(function (response) {
    _71
    const token = response.data.token;
    _71
    resolve(token);
    _71
    })
    _71
    .catch(function (error) {
    _71
    console.log(error);
    _71
    });
    _71
    })
    _71
    }
    _71
    _71
    async function loginRTM()
    _71
    {
    _71
    _71
    // Your app ID
    _71
    const appID = "<Your App ID>"
    _71
    _71
    // Initialize the client
    _71
    const client = AgoraRTM.createInstance(appID)
    _71
    _71
    // Display connection state changes
    _71
    client.on('ConnectionStateChanged', function (state, reason) {
    _71
    console.log("State changed To: " + state + " Reason: " + reason)
    _71
    })
    _71
    _71
    // Set Signaling user ID
    _71
    options.uid = "1234"
    _71
    // Get Token
    _71
    options.token = await fetchToken(options.uid)
    _71
    // Log in to Signaling
    _71
    await client.login(options)
    _71
    _71
    while (!stopped)
    _71
    {
    _71
    // Renew a token every 30 seconds for demonstration purposes.
    _71
    // Agora recommends that you renew a token regularly, such as every hour, in production.
    _71
    await sleep(30000)
    _71
    options.token = await fetchToken(options.uid)
    _71
    client.renewToken(options.token)
    _71
    _71
    let currentDate = new Date();
    _71
    let time = currentDate.getHours() + ":" + currentDate.getMinutes() + ":" + currentDate.getSeconds();
    _71
    _71
    console.log("Renew Signaling token at " + time)
    _71
    }
    _71
    _71
    }
    _71
    _71
    loginRTM()

  5. Open index.html with a supported browser to perform the following actions:

  • Successfully logging in to Signaling.

  • Renewing a token every 30 seconds.

Reference

This section introduces token generator libraries, version requirements, and related documents about tokens.

Token generator libraries

Agora provides an open-source AgoraDynamicKey repository on GitHub, which enables you to generate tokens on your server with programming languages such as C++, Java, and Go.

API reference

This section introduces the method to generate a Signaling token. Take C++ as an example:

  • AccessToken2


    _7
    func BuildToken(appId string, appCertificate string, userId string, expire uint32) (string, error) {
    _7
    token := accesstoken.NewAccessToken(appId, appCertificate, expire)
    _7
    serviceRtm := accesstoken.NewServiceRtm(userId)
    _7
    serviceRtm.AddPrivilege(accesstoken.PrivilegeLogin, expire)
    _7
    token.AddService(serviceRtm)
    _7
    return token.Build()
    _7
    }

    ParameterDescription
    appIdThe App ID of your Agora project.
    appCertificateThe App Certificate of your Agora project.
    userIdThe user ID of the Signaling system. You need specify the user ID yourself. See the userId parameter of the login method for supported character sets.
    expireThe duration (in seconds) from the generation of AccessToken2 to the expiration of AccessToken2. For example, if you set it as 600, the AccessToken2 expires 10 minutes after generation. An AccessToken2 is valid for a maximum of 24 hours. If you set it to a duration longer than 24 hours, the AccessToken2 still expires after 24 hours. If you set it to 0, the AccessToken2 expires immediately.
  • AccessToken


    _5
    static std::string buildToken(const std::string& appId,
    _5
    const std::string& appCertificate,
    _5
    const std::string& userAccount,
    _5
    RtmUserRole userRole,
    _5
    uint32_t privilegeExpiredTs = 0);

    ParameterDescription
    appIdThe App ID of your Agora project.
    appCertificateThe App Certificate of your Agora project.
    userAccountThe user ID of Signaling. You need specify the user ID yourself. See the userId parameter of the login method for supported character sets.
    roleThe user role. Agora supports only one user role. Set the value as the default value Rtm_User.
    privilegeExpiredTsThe Unix timestamp (s) when the token expires, represented by the sum of the current timestamp and the valid time of the token. This parameter is currently invalid. You can ignore this parameter. a Signaling token is valid for 24 hours.

Upgrade AccessToken2

This section introduces how to upgrade from AccessToken to AccessToken2 by example.

Prerequisites

  • You have deployed a token server and a web client for AccessToken in a previous version.
  • You have integrated an SDK version that supports AccessToken2.

Update the token server

  1. Replace the rtmtokenbuilder import statement:

_12
// Replace "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/RtmTokenBuilder"
_12
// with "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtmtokenbuilder2".
_12
import (
_12
rtmtokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtmtokenbuilder2"
_12
"fmt"
_12
"log"
_12
"net/http"
_12
"time"
_12
"encoding/json"
_12
"errors"
_12
"strconv"
_12
)

  1. Update the BuildToken function:

_3
// Previously, it is `result, err := rtmtokenbuilder.BuildToken(appID, appCertificate, rtm_uid, rtmtokenbuilder.RoleRtmUser, expireTimestamp)`.
_3
// Now, remove `rtmtokenbuilder.RoleRtmUser`.
_3
result, err := rtmtokenbuilder.BuildToken(appID, appCertificate, rtm_uid, expireTimestamp)

Test the AccessToken2 server

The client does not require any updates; however, the expiration logic changes accordingly.

To use AccessToken2 in server-side, refer to RESTful API Authentication and authenticate with new request headers.

Considerations

User ID

The user ID that you use to generate the Signaling token must be the same as the one you use to log in to Signaling.

App Certificate and Signaling token

To use the Signaling token for authentication, you need to enable the App Certificate for your project on Console. Once a project has enabled the App Certificate, you must use Signaling tokens to authenticate its users.

Signaling Token expiration

  • AccessToken2

    AccessToken2 allows you to specify the validity period of an Signaling token in seconds based on your business requirements. The validity period can be a maximum of 24 hours.

    When a token is due to expire in 30 seconds, the Signaling SDK triggers the onTokenPrivilegeWillExpire callback. Upon receiving this callback, you can generate a new Signaling token on your app server and call renewToken to pass the new Signaling token to the SDK.

    When an Signaling token expires, the subsequent logic varies depending on the connection state of the SDK:

    • If the Signaling SDK is in the CONNECTION_STATE_CONNECTED state, users receive the onTokenExpired callback and the onConnectionStateChanged callback caused by CONNECTION_CHANGE_REASON_TOKEN_EXPIRED (9), notifying them that the connection state of the SDK switches to CONNECTION_STATE_ABORTED. In this case, users need to log in again via the login method.
    • If the Signaling SDK is in the CONNECTION_STATE_RECONNECTING state, users receive the onTokenExpired callback when the network reconnects. In this case, users need to renew the token via the renewToken method.

    Although you can use the onTokenPrivilegeWillExpire and onTokenExpired callbacks to handle token expiration conditions, Agora recommends that you regularly renew the token (such as every hour) to keep the token valid.

    The names of methods, callbacks, and enums mentioned in the above section only apply to C++. Refer to the API documentation for names in other platforms.

  • AccessToken

    a Signaling token is valid for 24 hours.

    When the Signaling SDK is in the CONNECTION_STATE_CONNECTED state, the user remains online even if the Signaling token expires. If a user logs in with an expired Signaling token, the Signaling SDK returns the LOGIN_ERR_TOKEN_EXPIRED error.

    The Signaling SDK triggers the onTokenExpired callback only when a Signaling token expires and the Signaling SDK is in the CONNECTION_STATE_RECONNECTING state. The callback is triggered only once. Upon receiving this callback, you can generate a new Signaling token on your app server, and call renewToken to pass the new Signaling token to the SDK.

Page Content