# ขั้นตอนการสร้าง Authorization Token สำหรับใช้งาน DRM License Server

เพื่อให้ player สามารถเล่น content ที่ได้รับการป้องกันด้วย ByteArk Multi-DRM player จะต้องทำการขอ license ไปที่ license server ของทาง ByteArk ซึ่งจะทำการ authorize โดยการตรวจสอบ Authorization header ของ request นั้น ว่ามี JWT Token ที่ถูกต้องหรือไม่ โดยต้องมี 2 tokens แยกกันสำหรับ Widevine และ FairPlay

# ลำดับการทำงานในการใช้งาน Authorization Token

multi drm authorization sequence

# ขั้นตอนการสร้าง Token เพื่อนำไปใช้กับ player มีดังนี้

  1. ให้ทำการสร้าง public-private key pair ซึ่งสามารถใช้ algorithm ECDSA, RSA หรือ EdDSA เท่านั้น
  2. เก็บ private key ไว้เป็นความลับ แล้วนำ public key มา upload ผ่านระบบของ ByteArk Stream หรือในกรณีเป็น live streaming สามารถติดต่อผู้ประสานงานเพื่อประสานช่องทางการส่ง key
  3. หลังจากส่ง public key จะได้รับข้อมูลที่จำเป็นในการสร้าง token เพิ่มเติมคือ
    1. key_id สำหรับ Widevine ซึ่งจะเป็น integer
    2. key_id สำหรับ FairPlay ซึ่งจะเป็น string
  4. สร้าง JWT Token สองอันเพื่อใช้กับ Widevine และ FairPlay, sign ด้วย algorithm ES256, RS256 หรือ EdDSA ซึ่งจะต้องสัมพันธ์กับ keypair ที่สร้างในข้อ 1 และจะใช้ private key ข้างต้นในการ sign Token จะต้องมีส่วนประกอบที่บังคับ (ใส่อื่นๆได้แต่ห้ามขาด) คือ
    1. Header kid ซึ่งจะต้องมีค่าตรงกับ key_id ของ Widevine หรือ FairPlay ที่ได้มาในข้อ 3
    2. Payload exp ซึ่งกำหนดอายุการใช้งานของ token นี้ สามารถกำหนดได้ตามต้องการ, เป็น Unix timestamp

# ตัวอย่างการสร้าง JWT Token ผ่านหน้าเว็บ https://jwt.io/ (opens new window)

jwt.io

# ตัวอย่างการสร้าง JWT ด้วย Code

สามารถนำตัวอย่าง method generateJWT ไปใช้งานกับ framework ที่ท่านใช้อยู่ได้

# ตัวอย่าง Code Golang สำหรับสร้าง JWT Token

package main
import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"time"
	"github.com/golang-jwt/jwt/v5"
)
func generateJWT(privateKeyPEM []byte, kid interface{}) (string, error) {
	key, err := jwt.ParseECPrivateKeyFromPEM(privateKeyPEM)
	if err != nil {
		return "", fmt.Errorf("error parsing private key: %w", err)
	}
	claims := jwt.MapClaims{
		"sub": "license_auth",
		"exp": time.Now().Add(24 * time.Hour).Unix(),
	}
	token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
	token.Header["kid"] = kid
	signedToken, err := token.SignedString(key)
	if err != nil {
		return "", fmt.Errorf("error signing token: %w", err)
	}
	return signedToken, nil
}
func main() {
	privateKeyPEM, err := os.ReadFile("private_key.pem")
	if err != nil {
		log.Fatalf("Error reading private key: %v", err)
	}
	widevineToken, err := generateJWT(privateKeyPEM, 12345678)
	if err != nil {
		log.Fatalf("Widevine token error: %v", err)
	}
	fairplayToken, err := generateJWT(privateKeyPEM, "testtestwidevine")
	if err != nil {
		log.Fatalf("FairPlay token error: %v", err)
	}
	output := map[string]string{
		"widevine": widevineToken,
		"fairplay": fairplayToken,
	}
	jsonOutput, err := json.MarshalIndent(output, "", "  ")
	if err != nil {
		log.Fatalf("Error formatting JSON: %v", err)
	}
	fmt.Println(string(jsonOutput))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

# ตัวอย่าง Code Node.js สำหรับสร้าง JWT Token

npm install --save jsonwebtoken
1
const jwt = require('jsonwebtoken');
const fs = require('fs');
// Load private key
const privateKey = fs.readFileSync('private_key.pem', 'utf8');
function generateJWT(privateKey, kid) {
  const claims = {
    sub: 'license_auth',
    exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24
  };
  return jwt.sign(claims, privateKey, {
    algorithm: 'ES256',
    header: { kid }
  });
}
try {
  const widevineToken = generateJWT(privateKey, 12345678);
  const fairplayToken = generateJWT(privateKey, 'testtestwidevine');
  const output = {
    widevine: widevineToken,
    fairplay: fairplayToken
  };
  console.log(JSON.stringify(output, null, 2));
} catch (err) {
  console.error('Error generating token:', err);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# ตัวอย่าง Code PHP สำหรับสร้าง JWT Token

composer require firebase/php-jwt
1
<?php
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
/**
 * @param string $privateKey
 * @param string|int $kid
 * @return string
 */
function generateJWT(string $privateKey, string|int $kid): string {
    $payload = [
        'sub' => 'license_auth',
        'exp' => time() + 60 * 60 * 24
    ];
    $headers = ['kid' => $kid];
    return JWT::encode($payload, $privateKey, 'ES256', null, $headers);
}
$privateKey = file_get_contents('private_key.pem');
$tokens = [
    'widevine' => generateJWT($privateKey, 12345678),
    'fairplay' => generateJWT($privateKey, 'testtestwidevine'),
];
header('Content-Type: application/json');
echo json_encode($tokens, JSON_PRETTY_PRINT);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# การสร้าง Public-Private Keypair

สามารถสร้างได้หลากหลายวิธี จะแนะนำวิธีการสร้างด้วย command line tool openssl

# สร้างผ่าน OpenSSL command line tools

# ตัวอย่างกาสร้าง RSA public-private keypair

# Generate private key
openssl genpkey -algorithm RSA -out rsa_private.pem -pkeyopt rsa_keygen_bits:2048
# Extract public key
openssl rsa -pubout -in rsa_private.pem -out rsa_public.pem
1
2
3
4
5

# ตัวอย่างกาสร้าง ECDSA public-private keypair

# Generate private key
openssl ecparam -name secp256r1 -genkey -noout -out ecdsa_private.pem
# Extract public key
openssl ec -in ecdsa_private.pem -pubout -out ecdsa_public.pem
1
2
3
4
5

# ตัวอย่างกาสร้าง EdDSA public-private keypair

# Generate private key
openssl genpkey -algorithm Ed25519 -out ed25519_private.pem
# Extract public key
openssl pkey -in ed25519_private.pem -pubout -out ed25519_public.pem
1
2
3
4
5