# ขั้นตอนการสร้าง 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
- ผู้ชมเปิดหน้าที่มี Video Player เพื่อรับชม content
- Video Player ขอ JWT Authorization Token จาก Backend API ของคุณ
- Backend API ของคุณ สร้าง JWT Token สำหรับ Widevine และ FairPlay ด้วย private key แล้วส่งกลับไปให้ Video Player
- Config Video Player โดยนำ JWT Token ที่ได้มาใส่ใน
Authorizationheader ของ DRM config ในรูปแบบBearer <JWT Token>ดูรายละเอียดที่การตั้งค่า Video Player - Video Player ขอ license จาก ByteArk DRM License Server พร้อม Authorization header ซึ่ง license server จะตรวจสอบ token ก่อนส่ง license กลับมา
- Video Player ดึง content จาก ByteArk แล้วใช้ license ในการถอดรหัสเพื่อเล่น content
# ขั้นตอนการสร้าง Token เพื่อนำไปใช้กับ player
ให้ทำการสร้าง public-private key pair ซึ่งสามารถใช้ algorithm
ECDSA,RSAหรือEdDSAเท่านั้นหลังจากสร้าง public-private key pair แล้ว กรุณาเก็บ private key ไว้เป็นความลับ
นำ public key มา upload ผ่านระบบของ ByteArk Stream โดยไปที่เมนู Manage > Multi-DRM แล้วไปที่หัวข้อ "สร้างและอัปโหลดกุญแจสำหรับยืนยันตัวตน" หรือในกรณีเป็น live streaming สามารถติดต่อผู้ประสานงานเพื่อประสานช่องทางการส่ง public key

หลังจากส่ง public key จะได้รับข้อมูลที่จำเป็นในการสร้าง token เพิ่มเติม ซึ่งดูได้จากปุ่ม "ดูข้อมูลกุญแจที่ใช้งานอยู่" ในหน้า Multi-DRM คือ
key_id สำหรับ Widevine ซึ่งจะเป็น integer ดูได้จาก Service key ID (Widevine) ในหัวข้อ Widevine Preview Key

key_id สำหรับ FairPlay ซึ่งจะเป็น string ดูได้จาก Service key ID (FairPlay) ในหัวข้อ FairPlay Preview Key

สร้าง JWT Token สองอันเพื่อใช้กับ Widevine และ FairPlay, sign ด้วย algorithm
ES256,RS256หรือEdDSAซึ่งจะต้องสัมพันธ์กับ keypair ที่สร้างในข้อ 1 และจะใช้ private key ข้างต้นในการ sign Token จะต้องมีส่วนประกอบที่บังคับ (ใส่อื่นๆได้แต่ห้ามขาด) คือ- Header
kidซึ่งจะต้องมีค่าตรงกับ key_id ของ Widevine หรือ FairPlay ที่ได้มาในข้อ 4 - Payload
expซึ่งกำหนดอายุการใช้งานของ token นี้ สามารถกำหนดได้ตามต้องการ, เป็น Unix timestamp
- Header
นำ JWT Token ไปตั้งค่าใน Video Player
# การสร้าง Public-Private Keypair
สามารถสร้างได้หลากหลายวิธี จะแนะนำวิธีการสร้างด้วย command line tool openssl
# ตัวอย่างการสร้าง 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
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
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
2
3
4
5
# การสร้าง JWT Token
ตัวอย่างการสร้าง JWT ด้วย Code Node.js, Go, PHP สามารถนำตัวอย่าง method generateJWT ไปใช้งานกับ framework ที่ท่านใช้อยู่ได้
# Node.js
npm install --save jsonwebtoken
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);
}
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
# Node.js (Backend API สำหรับส่ง Token ให้ Video Player)
ตัวอย่าง Backend API ด้วย Express (opens new window) ที่สร้าง JWT Token ทั้งสองอันแล้วส่งกลับไปให้ Video Player ตามลำดับการทำงานข้างต้น ควรตรวจสอบสิทธิ์ของผู้ชมก่อนสร้าง token ทุกครั้ง
npm install --save express jsonwebtoken
const express = require('express');
const jwt = require('jsonwebtoken');
const fs = require('fs');
const privateKey = fs.readFileSync('private_key.pem', 'utf8');
const WIDEVINE_KEY_ID = 12345678;
const FAIRPLAY_KEY_ID = 'testtestwidevine';
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 }
});
}
const app = express();
app.get('/drm-tokens', (req, res) => {
// TODO: Authenticate the viewer and check their permission before issuing tokens
try {
res.json({
widevine: generateJWT(privateKey, WIDEVINE_KEY_ID),
fairplay: generateJWT(privateKey, FAIRPLAY_KEY_ID)
});
} catch (err) {
console.error('Error generating token:', err);
res.status(500).json({ error: 'Failed to generate token' });
}
});
app.listen(3000);
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
# Golang
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))
}
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
# PHP
composer require firebase/php-jwt
<?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);
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
# การตั้งค่า Video Player
หลังจาก Video Player ได้รับ JWT Token จาก Backend API แล้ว ให้นำ token ไปใส่ใน licenseRequestHeaders ของ DRM config โดยใช้ header ชื่อ Authorization และค่าเป็น Bearer <JWT Token> ซึ่ง token ของ Widevine และ FairPlay จะต้องใช้แยกกันตามระบบ DRM
แต่ละระบบ DRM ใช้รูปแบบวิดีโอ (format) ต่างกัน ดังนี้
| DRM | Format | type | Browser |
|---|---|---|---|
| FairPlay | HLS (.m3u8) | application/x-mpegURL | Safari บน macOS, iOS, iPadOS |
| Widevine | MPEG-DASH (.mpd) | application/dash+xml | Chrome, Edge, Firefox และ Android |
ตัวอย่างการตั้งค่า ByteArk Player for Web โดยตรวจสอบว่า browser รองรับ FairPlay หรือไม่ แล้วเลือก source และ token ให้ตรงกับระบบ DRM
// Tokens returned from your backend API, e.g. GET /drm-tokens
var tokens = {
widevine: '<WIDEVINE_JWT_TOKEN>',
fairplay: '<FAIRPLAY_JWT_TOKEN>'
};
// FairPlay is available only in Safari (macOS, iOS, iPadOS)
var isFairPlaySupported = typeof window.WebKitMediaKeys !== 'undefined';
var source = isFairPlaySupported
? {
src: '<HLS_M3U8_URL>', // .m3u8 for FairPlay
type: 'application/x-mpegURL',
drm: {
fairplay: {
certificateUrl: '<URL_TO_CERTIFICATE>',
processSpcUrl: '<URL_TO_FAIRPLAY_LICENSE_SERVER>',
licenseRequestHeaders: [
{
name: 'Authorization',
value: 'Bearer ' + tokens.fairplay
}
]
}
}
}
: {
src: '<DASH_MPD_URL>', // .mpd for Widevine
type: 'application/dash+xml',
drm: {
widevine: {
url: '<URL_TO_WIDEVINE_LICENSE_SERVER>',
licenseRequestHeaders: [
{
name: 'Authorization',
value: 'Bearer ' + tokens.widevine
}
]
}
}
};
var player = bytearkPlayer('video-player', {
fluid: true,
sources: [source],
plugins: {
bytearkShaka: {}
}
});
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
Note
token มีอายุตามค่า exp ที่กำหนดไว้ หาก token หมดอายุ license server จะปฏิเสธ request ดังนั้น Video Player ควรขอ token ใหม่จาก Backend API ก่อนเริ่มเล่น content ทุกครั้ง
# การตั้งค่า DRM ของ ByteArk Player แต่ละแพลตฟอร์ม
ดูรายละเอียดการตั้งค่า DRM และการส่ง Authorization header ของ ByteArk Player แต่ละแพลตฟอร์มได้ที่
| แพลตฟอร์ม | Widevine | FairPlay |
|---|---|---|
| Web | Google Widevine | Apple FairPlay |
| iOS | - | Apple FairPlay |
| Android | Google Widevine | - |
| Flutter | Google Widevine | Apple FairPlay |