Implement token-based authentication

Updated at:
Copy as MD

Tokens are security credentials that protect your cloud services from unauthorized access. For ApsaraVideo Real-time Communication (ARTC), your app server generates a token and sends it to the client. The client-side ARTC SDK uses the token to join a channel, and access is granted only after successful authentication.

Prerequisites

  • An Alibaba Cloud account is created, and ApsaraVideo Live is activated.

  • An ARTC application is created, and the AppID and AppKey are obtained.

Sample code

Server-side token generation (recommended)

ApsaraVideo Live provides token generation code samples for multiple languages, including Go, Java, and Python. For details, visit GitHub repository.

Client-side token generation (for development and testing only)

Important

Token generation requires an AppKey. Hardcoding the AppKey on the client creates a security risk. In production environments, generate tokens on your business server and send them to the client.

If server-side token generation is not yet implemented, you can use the sample code in APIExample to generate temporary tokens during development and testing:

Android: Android/ARTCExample/KeyCenter/src/main/java/com/aliyun/artc/api/keycenter/ARTCTokenHelper.java

iOS: iOS/ARTCExample/Common/ARTCTokenHelper.swift

Joining a channel with a token

Note

For all live and production applications, tokens must be generated on a server and passed to the client. The client-side token generation method in the examples below is insecure and intended for debugging and demonstration purposes only.

Android: Android/ARTCExample/QuickStart/src/main/java/com/aliyun/artc/api/quickstart/TokenGenerate/TokenGenerateActivity.java
iOS: iOS/ARTCExample/QuickStart/TokenGenerate/TokenGenerateVC.swift

How it works

Procedure

image
  1. The client requests a token from your app server. The app server generates a token according to predefined rules and returns it to the client.

  2. The client uses the received token, along with other required information such as appIdchannelId, and userId, to send a request to join the specified channel.

  3. The ARTC server verifies the token and admits the client into the channel upon successful verification.

Token generation method

The following table describes the fields used for token generation.

Field

Description

AppID

The application ID and key automatically generated when you create an ARTC application in the console. For more information, see Get development parameters.

AppKey

ChannelID

The custom channel ID. It must be a string and can contain digits, letters, hyphens (-), and underscores (_), with a maximum length of 64 characters.

All participants in the same session (the host and co-hosts) must use the same ChannelID.

UserId

The custom user ID. It must be a string and can contain digits, letters, hyphens (-), and underscores (_), with a maximum length of 64 characters.

Nonce

A nonce string. We recommend that you leave this field empty.

Timestamp

The token expiration timestamp, in seconds. A recommended value is 24 hours: add 86400 (24 × 60 × 60) to the current UNIX timestamp.

Token generation procedure:

yuque_diagram (1)

Code example:

// 1. Concatenate the following fields: AppID+AppKey+ChannelID+UserID+Nonce+Timestamp
// 2. Use the sha256 function on the concatenated string to generate the token.
token = sha256(AppID+AppKey+ChannelID+UserID+Nonce+Timestamp)
// Example:
AppID = "abc",AppKey="abckey",ChannelID="abcChannel",UserID="abcUser",Nonce="",Timestamp=1699423634
token = sha256("abcabckeyabcChannelabcUser1699423634") = "3c9ee8d9f8734f0b7560ed8022a0590659113955819724fc9345ab8eedf84f31"

General ARTC scenarios

Use a token to authenticate a client that joins a channel. Examples are provided for Android and iOS.

The ARTC SDK provides two methods for joining a channel: single-parameter and multi-parameter. These are foundational methods applicable to all ARTC scenarios.

Single-parameter method (recommended)

The ARTC SDK provides a simplified, single-parameter API for joining a channel. This method is recommended because it prevents join failures caused by inconsistencies between server-generated parameters and client-supplied values.

To use this method, combine the Token, AppID, ChannelID, Nonce, UserID, and Timestamp parameters into a JSON object. Then, Base64-encode the JSON string to generate a new authentication string (Base64 token).

Note

When you submit a ticket for troubleshooting, provide the Base64 token or the userName passed in the joinChannel call.

yuque_diagram (3)

Server-side: Generate Base64 token

Java

package com.example;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Calendar;
import org.json.JSONObject;

public class App {

    public static String createBase64Token(String appid, String appkey, String channelid, String userid) {
        // Calculate the expiration timestamp (24 hours from now)
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.HOUR_OF_DAY, 24);
        long timestamp = calendar.getTimeInMillis() / 1000;

        // Concatenate the strings
        String stringBuilder = appid + appkey + channelid + userid + timestamp;

        // Calculate the SHA-256 hash
        String token = sha256(stringBuilder);

        // Create the JSON object
        JSONObject base64tokenJson = new JSONObject();
        base64tokenJson.put("appid", appid);
        base64tokenJson.put("channelid", channelid);
        base64tokenJson.put("userid", userid);
        base64tokenJson.put("nonce", "");
        base64tokenJson.put("timestamp", timestamp);
        base64tokenJson.put("token", token);

        // Convert the JSON object to a string and encode it in Base64
        String jsonStr = base64tokenJson.toString();
        String base64token = Base64.getEncoder().encodeToString(jsonStr.getBytes(StandardCharsets.UTF_8));
        return base64token;
    }

    private static String sha256(String input) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1)
                    hexString.append('0');
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        String appid = "your_appid";
        String appkey = "your_appkey";
        String channel_id = "your_channel_id";
        String user_id = "your_user_id";

        String base64token = createBase64Token(appid, appkey, channel_id, user_id);
        System.out.println("Base64 Token: " + base64token);
    }
}

Go

package main

import (
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"time"
)

func createBase64Token(appid, appkey, channelID, userID string) (string, error) {
	// Calculate the expiration timestamp (24 hours from now)
	timestamp := time.Now().Add(24 * time.Hour).Unix()

	// Concatenate the strings
	stringBuilder := appid + appkey + channelID + userID + fmt.Sprintf("%d", timestamp)

	// Calculate the SHA-256 hash
	hasher := sha256.New()
	hasher.Write([]byte(stringBuilder))
	token := hasher.Sum(nil)

	// Convert the hash to a hexadecimal string using encoding/hex
	tokenHex := hex.EncodeToString(token)

	// Create the JSON object
	tokenJSON := map[string]interface{}{
		"appid":     appid,
		"channelid": channelID,
		"userid":    userID,
		"nonce":     "",
		"timestamp": timestamp,
		"token":     tokenHex,
	}

	// Convert the JSON object to a string and encode it in Base64
	jsonBytes, err := json.Marshal(tokenJSON)
	if err != nil {
		return "", err
	}
	base64Token := base64.StdEncoding.EncodeToString(jsonBytes)

	return base64Token, nil
}

func main() {
	appid := "your_appid"
	appkey := "your_appkey"
	channelID := "your_channel_id"
	userID := "your_user_id"

	token, err := createBase64Token(appid, appkey, channelID, userID)
	if err != nil {
		fmt.Println("Error creating token:", err)
		return
	}
	fmt.Println("Base64 Token:", token)
}

Python

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import hashlib
import datetime
import time
import base64
import json

def create_base64_token(app_id, app_key, channel_id, user_id):
    expire = datetime.datetime.now() + datetime.timedelta(days=1)
    timestamp = int(time.mktime(expire.timetuple()))
    h = hashlib.sha256()
    h.update(str(app_id).encode('utf-8'))
    h.update(str(app_key).encode('utf-8'))
    h.update(str(channel_id).encode('utf-8'))
    h.update(str(user_id).encode('utf-8'))
    h.update(str(timestamp).encode('utf-8'))
    token = h.hexdigest()

    jsonToken = {'appid':app_id,
                 'channelid':channel_id,
                 'userid':user_id,
                 'nonce':'',
                 'timestamp':timestamp,
                 'token':token
                }
    base64Token = base64.b64encode(json.dumps(jsonToken).encode())

    return base64Token

def main():
    app_id = 'your_appid'
    app_key = 'your_appkey'
    channel_id = 'your_channel_id'
    user_id = 'your_user_id'

    base64Token = create_base64_token(app_id, app_key, channel_id, user_id)
    
    print(base64Token)

if __name__ == '__main__':
    main()

Node.js

'use strict'
const crypto = require('crypto')
function create_base64_token(appid, appkey, channelid, userid) {
    let timestamp = Math.floor(Date.now() / 1000 + 24 * 60 * 60)

    let string_builder = appid + appkey + channelid + userid + timestamp.toString()
    let token = crypto.createHash('sha256').update(string_builder).digest('hex')
    let base64tokenJson = {
        appid:appid,
        channelid:channelid,
        userid:userid,
        nonce:'',
        timestamp:timestamp,
        token:token
    }
    let base64token = Buffer.from(JSON.stringify(base64tokenJson), 'utf-8').toString('base64')
    return base64token
}

let appid = "your_appid";
let appkey = "your_appkey";
let channel_id = "your_channel_id";
let user_id = "your_user_id";

let base64token = create_base64_token(appid, appkey, channel_id, user_id)
console.log(base64token)

xRust

use chrono::{Duration, Utc};
use sha2::{Sha256, Digest};
use serde_json::json;
use base64::encode;

fn create_base64_token(appid: &str, appkey: &str, channel_id: &str, user_id: &str) -> String {
    // Calculate the expiration timestamp (24 hours from now)
    let timestamp = (Utc::now() + Duration::hours(24)).timestamp();

    // Concatenate the strings
    let string_builder = format!("{}{}{}{}{}", appid, appkey, channel_id, user_id, timestamp);

    // Calculate the SHA-256 hash
    let mut hasher = Sha256::new();
    hasher.update(string_builder);
    let token = hasher.finalize();
    let token_hex = format!("{:x}", token);

    // Create the JSON object
    let token_json = json!({
        "appid": appid,
        "channelid": channel_id,
        "userid": user_id,
        "nonce": "",
        "timestamp": timestamp,
        "token": token_hex
    });

    // Convert the JSON object to a string and encode it in Base64
    let base64_token = encode(token_json.to_string());

    base64_token
}

fn main() {
    let appid = "your_appid";
    let appkey = "your_appkey";
    let channel_id = "your_channel_id";
    let user_id = "your_user_id";

    let token = create_base64_token(appid, appkey, channel_id, user_id);
    println!("Base64 Token: {}", token);
}

Client-side: Call joinChannel

After receiving the Base64 token from the server, the client calls the joinChannel API to join a channel:

  • Android:

    // You can pass null for channelId and userId, as their values are included in the Base64 token. If you do provide them, they must match the values used to generate the token. This feature can be used to verify parameter consistency between your server and the client. 
    // base64Token is the Base64-encoded token.
    // username is an identity that you can pass for troubleshooting.
    mAliRtcEngine.joinChannel(base64Token, null, null, "username");
  • iOS:

    // You can pass null for channelId and userId, as their values are included in the Base64 token. If you do provide them, they must match the values used to generate the token. This feature can be used to verify parameter consistency between your server and the client. 
    // base64Token is the Base64-encoded token.
    // username is an identity that you can pass for troubleshooting.
    [self.engine joinChannel:base64Token channelId:nil userId:nil name:@"username" onResultWithUserId:nil];

Multi-parameter method

The ARTC SDK also provides a multi-parameter API for joining a channel. This method uses the AliRtcAuthInfo data structure to pass the token and user information.

Important

The channelId and userId must match those used to generate the token.

  • Android:

    // Pass the token and user information.
    AliRtcAuthInfo authInfo = new AliRtcAuthInfo();
    authInfo.appId = appId;
    authInfo.channelId = channelId;
    authInfo.userId = userId;
    authInfo.timestamp = timestamp;
    authInfo.nonce = nonce;
    authInfo.token = token;
    // Join the channel.
    mAliRtcEngine.joinChannel(authInfo, "");
  • iOS:

    // Pass the token and user information.
    let authInfo = AliRtcAuthInfo()
    authInfo.appId = appId
    authInfo.channelId = channelId
    authInfo.nonce = nonce
    authInfo.userId = userId
    authInfo.timestamp = timestamp
    authInfo.token = authToken
    // Join the channel.
    self.rtcEngine?.joinChannel(authInfo, name: nil)

Co-streaming scenarios

For co-streaming scenarios, the ARTC SDK supports a streamlined authentication method. You can append Token, AppID, ChannelID, Nonce, UserID, and Timestamp fields as query parameters to the artc:// URL for co-streaming. For details about ARTC URL generation, see Co-streaming URL rules.

The following diagram illustrates the co-streaming authentication workflow:

image

Example URLs for co-streaming or live battle

Push URL:

artc://live.aliyun.com/push/633?timestamp=1685094092&token=fe4e674ade****6686&userId=718&sdkAppId=xxx

Pull URL:

artc://live.aliyun.com/play/633?timestamp=1685094092&token=fe4e674ade****6686&userId=718&sdkAppId=xxx
Note

live.aliyun.com is a fixed prefix for co-streaming URLs and is not a resolvable domain name. It cannot be used for domain-related operations, such as ping, traceroute, or telnet.

Token expiration handling

The Timestamp field in a token specifies its expiration time.

After a user joins a channel with a token:

  • 30 seconds before the token expires, the SDK triggers the onAuthInfoWillExpire callback. Call the refreshAuthInfo method to update the authentication information.

  • When the token expires, the SDK triggers the onAuthInfoExpired callback. To remain in the channel, the user must join the channel again.