Skip to content
Back to Blog
1 min read

Content Protection and DRM with Azure Media Services

I wrote “2021-09-27-azure-content-protection” to share practical, production-minded guidance on this topic.

DRM Options

DRM SystemPlatformsLicense Server
PlayReadyWindows, Xbox, Smart TVsMicrosoft
WidevineChrome, Android, ChromecastGoogle
FairPlaySafari, iOS, Apple TVApple
AES-128All (clear key)Azure

Creating Content Key Policies

from azure.mgmt.media import AzureMediaServices
from azure.mgmt.media.models import (
    ContentKeyPolicy,
    ContentKeyPolicyOption,
    ContentKeyPolicyTokenRestriction,
    ContentKeyPolicySymmetricTokenKey,
    ContentKeyPolicyRestrictionTokenType,
    ContentKeyPolicyPlayReadyConfiguration,
    ContentKeyPolicyPlayReadyLicense,
    ContentKeyPolicyPlayReadyContentEncryptionKeyFromHeader,
    ContentKeyPolicyPlayReadyContentType,
    ContentKeyPolicyWidevineConfiguration,
    ContentKeyPolicyFairPlayConfiguration,
    ContentKeyPolicyClearKeyConfiguration
)
from azure.identity import DefaultAzureCredential
import base64
import secrets

class ContentProtectionManager:
    def __init__(self, subscription_id: str, resource_group: str, account_name: str):
        self.credential = DefaultAzureCredential()
        self.client = AzureMediaServices(self.credential, subscription_id)
        self.resource_group = resource_group
        self.account_name = account_name

    def create_multi_drm_policy(self, policy_name: str,
                                token_key: str,
                                issuer: str,
                                audience: str) -> ContentKeyPolicy:
        """Create a content key policy with multiple DRM options."""

        # Token verification key
        primary_key = ContentKeyPolicySymmetricTokenKey(
            key_value=base64.b64decode(token_key)
        )

        # Token restriction
        token_restriction = ContentKeyPolicyTokenRestriction(
            issuer=issuer,
            audience=audience,
            primary_verification_key=primary_key,
            restriction_token_type=ContentKeyPolicyRestrictionTokenType.JWT
        )

        options = []

        # PlayReady option
        playready_config = ContentKeyPolicyPlayReadyConfiguration(
            licenses=[
                ContentKeyPolicyPlayReadyLicense(
                    allow_test_devices=True,
                    content_key_location=ContentKeyPolicyPlayReadyContentEncryptionKeyFromHeader(),
                    content_type=ContentKeyPolicyPlayReadyContentType.UNSPECIFIED,
                    license_type="NonPersistent"
                )
            ]
        )
        options.append(ContentKeyPolicyOption(
            name="PlayReady",
            configuration=playready_config,
            restriction=token_restriction
        ))

        # Widevine option
        widevine_config = ContentKeyPolicyWidevineConfiguration(
            widevine_template="""{
                "allowed_track_types": "SD_HD",
                "content_key_specs": [{
                    "track_type": "SD",
                    "security_level": 1,
                    "required_output_protection": {"hdcp": "HDCP_NONE"}
                }],
                "policy_overrides": {
                    "can_play": true,
                    "can_persist": false,
                    "can_renew": false
                }
            }"""
        )
        options.append(ContentKeyPolicyOption(
            name="Widevine",
            configuration=widevine_config,
            restriction=token_restriction
        ))

        # FairPlay option (requires Apple certificate)
        # fairplay_config = ContentKeyPolicyFairPlayConfiguration(
        #     ask=fairplay_ask,
        #     fairplay_pfx=fairplay_pfx,
        #     fairplay_pfx_password=fairplay_password,
        #     rental_and_lease_key_type="PersistentUnlimited",
        #     rental_duration=0
        # )

        # Clear key option (AES-128)
        clear_key_config = ContentKeyPolicyClearKeyConfiguration()
        options.append(ContentKeyPolicyOption(
            name="ClearKey",
            configuration=clear_key_config,
            restriction=token_restriction
        ))

        policy = ContentKeyPolicy(options=options)

        return self.client.content_key_policies.create_or_update(
            self.resource_group,
            self.account_name,
            policy_name,
            policy
        )

Creating Streaming Policies

from azure.mgmt.media.models import (
    StreamingPolicy,
    CommonEncryptionCenc,
    CommonEncryptionCbcs,
    EnabledProtocols,
    StreamingPolicyContentKeys,
    DefaultKey,
    TrackSelection,
    TrackPropertyCondition,
    StreamingPolicyContentKey
)

def create_drm_streaming_policy(manager, policy_name: str,
                                content_key_policy_name: str) -> StreamingPolicy:
    """Create a streaming policy with DRM protection."""

    # CENC (Common Encryption) for PlayReady and Widevine
    cenc = CommonEncryptionCenc(
        enabled_protocols=EnabledProtocols(
            dash=True,
            hls=False,  # HLS uses CBCS
            smooth_streaming=True,
            download=False
        ),
        content_keys=StreamingPolicyContentKeys(
            default_key=DefaultKey(
                label="cenc_default",
                policy_name=content_key_policy_name
            )
        ),
        drm=CommonEncryptionCencDrm(
            play_ready=StreamingPolicyPlayReadyConfiguration(
                custom_license_acquisition_url_template=None
            ),
            widevine=StreamingPolicyWidevineConfiguration(
                custom_license_acquisition_url_template=None
            )
        )
    )

    # CBCS (Common Encryption CBCS) for FairPlay
    cbcs = CommonEncryptionCbcs(
        enabled_protocols=EnabledProtocols(
            dash=False,
            hls=True,
            smooth_streaming=False,
            download=False
        ),
        content_keys=StreamingPolicyContentKeys(
            default_key=DefaultKey(
                label="cbcs_default",
                policy_name=content_key_policy_name
            )
        )
    )

    policy = StreamingPolicy(
        common_encryption_cenc=cenc,
        common_encryption_cbcs=cbcs
    )

    return manager.client.streaming_policies.create(
        manager.resource_group,
        manager.account_name,
        policy_name,
        policy
    )

Generating JWT Tokens

import jwt
from datetime import datetime, timedelta
import uuid

class TokenService:
    def __init__(self, token_key: str, issuer: str, audience: str):
        self.token_key = token_key
        self.issuer = issuer
        self.audience = audience

    def generate_token(self, content_key_id: str = None,
                      expiry_hours: int = 24) -> str:
        """Generate a JWT token for content access."""

        now = datetime.utcnow()
        expiry = now + timedelta(hours=expiry_hours)

        claims = {
            "iss": self.issuer,
            "aud": self.audience,
            "iat": now,
            "exp": expiry,
            "nbf": now,
            "jti": str(uuid.uuid4())
        }

        # Add content key identifier if specified
        if content_key_id:
            claims["urn:microsoft:azure:mediaservices:contentkeyidentifier"] = content_key_id

        token = jwt.encode(
            claims,
            base64.b64decode(self.token_key),
            algorithm="HS256"
        )

        return token

    def generate_persistent_token(self, user_id: str,
                                  content_id: str) -> str:
        """Generate a token for offline playback."""

        claims = {
            "iss": self.issuer,
            "aud": self.audience,
            "iat": datetime.utcnow(),
            "exp": datetime.utcnow() + timedelta(days=30),
            "sub": user_id,
            "content_id": content_id,
            "urn:microsoft:azure:mediaservices:maxuses": "unlimited"
        }

        return jwt.encode(
            claims,
            base64.b64decode(self.token_key),
            algorithm="HS256"
        )

# Usage
token_service = TokenService(
    token_key="your-base64-encoded-key",
    issuer="https://yourapp.com",
    audience="urn:youraudience"
)

# Generate playback token
token = token_service.generate_token()
print(f"Bearer {token}")

Creating Protected Streaming Locators

from azure.mgmt.media.models import StreamingLocator

def create_protected_locator(manager, asset_name: str,
                            locator_name: str,
                            streaming_policy_name: str,
                            content_key_policy_name: str) -> dict:
    """Create a streaming locator with DRM protection."""

    locator = StreamingLocator(
        asset_name=asset_name,
        streaming_policy_name=streaming_policy_name,
        default_content_key_policy_name=content_key_policy_name
    )

    created_locator = manager.client.streaming_locators.create(
        manager.resource_group,
        manager.account_name,
        locator_name,
        locator
    )

    # Get streaming paths
    paths = manager.client.streaming_locators.list_paths(
        manager.resource_group,
        manager.account_name,
        locator_name
    )

    # Get content keys
    keys = manager.client.streaming_locators.list_content_keys(
        manager.resource_group,
        manager.account_name,
        locator_name
    )

    return {
        "locator_id": created_locator.streaming_locator_id,
        "streaming_paths": paths.streaming_paths,
        "content_keys": [
            {"id": k.id, "type": k.type, "label": k.label_reference_in_streaming_policy}
            for k in keys.content_keys
        ]
    }

Building a License Delivery Service

from flask import Flask, request, jsonify
import jwt

app = Flask(__name__)

# Configuration
TOKEN_KEY = "your-base64-encoded-key"
ISSUER = "https://yourapp.com"
AUDIENCE = "urn:youraudience"

def verify_token(token: str) -> dict:
    """Verify JWT token and return claims."""
    try:
        claims = jwt.decode(
            token,
            base64.b64decode(TOKEN_KEY),
            algorithms=["HS256"],
            audience=AUDIENCE,
            issuer=ISSUER
        )
        return claims
    except jwt.InvalidTokenError as e:
        raise ValueError(f"Invalid token: {e}")

@app.route('/api/token', methods=['POST'])
def get_playback_token():
    """Issue playback token for authenticated users."""

    # Verify user authentication
    auth_header = request.headers.get('Authorization')
    if not auth_header:
        return jsonify({"error": "Unauthorized"}), 401

    # Generate playback token
    user_id = request.json.get('user_id')
    content_id = request.json.get('content_id')

    # Check user entitlement (simplified)
    if not check_user_entitlement(user_id, content_id):
        return jsonify({"error": "Not entitled"}), 403

    token_service = TokenService(TOKEN_KEY, ISSUER, AUDIENCE)
    token = token_service.generate_token()

    return jsonify({
        "token": token,
        "expires_in": 86400  # 24 hours
    })

def check_user_entitlement(user_id: str, content_id: str) -> bool:
    """Check if user is entitled to access content."""
    # Implement your entitlement logic
    return True

@app.route('/api/license/playready', methods=['POST'])
def playready_license():
    """Custom PlayReady license acquisition (if using custom server)."""
    # Azure Media Services handles this by default
    pass

if __name__ == '__main__':
    app.run(port=5000)

Video Player Integration

// Azure Media Player with DRM
const playerOptions = {
    autoplay: true,
    controls: true,
    width: "640",
    height: "400",
    logo: { enabled: false }
};

const player = amp("azuremediaplayer", playerOptions);

// Get token from your backend
async function getPlaybackToken(contentId) {
    const response = await fetch('/api/token', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${userToken}`
        },
        body: JSON.stringify({ content_id: contentId })
    });
    return response.json();
}

// Set protected source
async function playProtectedContent(manifestUrl, contentId) {
    const { token } = await getPlaybackToken(contentId);

    player.src([{
        src: manifestUrl,
        type: "application/vnd.ms-sstr+xml",
        protectionInfo: [
            {
                type: "PlayReady",
                authenticationToken: `Bearer=${token}`
            },
            {
                type: "Widevine",
                authenticationToken: `Bearer=${token}`
            },
            {
                type: "FairPlay",
                authenticationToken: `Bearer=${token}`,
                certificateUrl: "https://yourapp.com/fairplay.cer"
            }
        ]
    }]);
}

playProtectedContent(
    "https://streaming.mediaservices.windows.net/.../manifest",
    "video-123"
);

Best Practices

  1. Multi-DRM: Support PlayReady, Widevine, and FairPlay for maximum reach
  2. Token Expiry: Use short-lived tokens (hours, not days)
  3. HTTPS Only: Always use HTTPS for license acquisition
  4. Secure Keys: Store token signing keys securely
  5. Monitor Usage: Track license requests for abuse detection
  6. Offline Support: Consider persistent licenses for offline viewing

Content protection with DRM ensures your premium content remains secure while providing a seamless viewing experience across all platforms.\n\n## Takeaways\n\nAdd a concise, personal takeaway and recommended next steps here.\n

Michael John Pena

Michael John Pena

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.