# ขั้นตอนการสร้าง 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

  1. ผู้ชมเปิดหน้าที่มี Video Player เพื่อรับชม content
  2. Video Player ขอ JWT Authorization Token จาก Backend API ของคุณ
  3. Backend API ของคุณ สร้าง JWT Token สำหรับ Widevine และ FairPlay ด้วย private key แล้วส่งกลับไปให้ Video Player
  4. Config Video Player โดยนำ JWT Token ที่ได้มาใส่ใน Authorization header ของ DRM config ในรูปแบบ Bearer <JWT Token> ดูรายละเอียดที่การตั้งค่า Video Player
  5. Video Player ขอ license จาก ByteArk DRM License Server พร้อม Authorization header ซึ่ง license server จะตรวจสอบ token ก่อนส่ง license กลับมา
  6. Video Player ดึง content จาก ByteArk แล้วใช้ license ในการถอดรหัสเพื่อเล่น content

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

  1. ให้ทำการสร้าง public-private key pair ซึ่งสามารถใช้ algorithm ECDSA, RSA หรือ EdDSA เท่านั้น

  2. หลังจากสร้าง public-private key pair แล้ว กรุณาเก็บ private key ไว้เป็นความลับ

  3. นำ public key มา upload ผ่านระบบของ ByteArk Stream โดยไปที่เมนู Manage > Multi-DRM แล้วไปที่หัวข้อ "สร้างและอัปโหลดกุญแจสำหรับยืนยันตัวตน" หรือในกรณีเป็น live streaming สามารถติดต่อผู้ประสานงานเพื่อประสานช่องทางการส่ง public key

    Manage Multi-DRM

  4. หลังจากส่ง public key จะได้รับข้อมูลที่จำเป็นในการสร้าง token เพิ่มเติม ซึ่งดูได้จากปุ่ม "ดูข้อมูลกุญแจที่ใช้งานอยู่" ในหน้า Multi-DRM คือ

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

      Widevine key_id

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

      FairPlay key_id

  5. สร้าง JWT Token สองอันเพื่อใช้กับ Widevine และ FairPlay, sign ด้วย algorithm ES256, RS256 หรือ EdDSA ซึ่งจะต้องสัมพันธ์กับ keypair ที่สร้างในข้อ 1 และจะใช้ private key ข้างต้นในการ sign Token จะต้องมีส่วนประกอบที่บังคับ (ใส่อื่นๆได้แต่ห้ามขาด) คือ

    1. Header kid ซึ่งจะต้องมีค่าตรงกับ key_id ของ Widevine หรือ FairPlay ที่ได้มาในข้อ 4
    2. Payload exp ซึ่งกำหนดอายุการใช้งานของ token นี้ สามารถกำหนดได้ตามต้องการ, เป็น Unix timestamp
  6. นำ 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
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

# การสร้าง JWT Token

ตัวอย่างการสร้าง JWT ด้วย Code Node.js, Go, PHP สามารถนำตัวอย่าง method generateJWT ไปใช้งานกับ framework ที่ท่านใช้อยู่ได้

# Node.js

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

# Node.js (Backend API สำหรับส่ง Token ให้ Video Player)

ตัวอย่าง Backend API ด้วย Express (opens new window) ที่สร้าง JWT Token ทั้งสองอันแล้วส่งกลับไปให้ Video Player ตามลำดับการทำงานข้างต้น ควรตรวจสอบสิทธิ์ของผู้ชมก่อนสร้าง token ทุกครั้ง

npm install --save express jsonwebtoken
1
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);
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

# 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))
}
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

# PHP

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

# การตั้งค่า 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: {}
  }
});
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

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