Secure your Streams with Signed URLs (Access Token)

By default, access and sharing of your Stream are available to anyone who has knowledge of its URL. This intentional accessibility is designed to facilitate seamless content sharing and embedding. Nevertheless, there are instances where it's necessary to safeguard your content and exercise precise control over who can access your Streams and for what duration. This is where our "Signed URL" feature becomes pivotal, enabling you to secure your Streams by generating signed URLs.

For users to access a signed Stream, they must authenticate themselves by providing a signed JSON Web Token (JWT) with their request. This token needs to be generated and signed with a private encryption key exclusively known to you. Optionally, this token can include an "expiration" parameter that specifies the duration of access to the signed Stream.

Getting Started:

Create a new signing key

The first step is to create a signing key by making aPOST request to the /signing-keys endpoint. This returns a base64 encoded private key in PEM format, which is required for signing your JSON Web Tokens.

🚧

Storing your signing key

Ensure the secure storage of your private key as we do not retain copies on our servers! If you happen to lose access to your private key, refer to the instructions provided in the Key Rotation section below to acquire a new private key.

Create a signed Stream

To protect a Stream with a Signed URL, you must set the property signed: true when creating a new Stream. Please note that it is currently not possible to convert already existing Streams to utilize Signed URLs, nor can Signed URLs be converted back to the previous format.

Once a signed Stream is created, it cannot be accessed right away or be previewed in the Dashboard unless a JWT signed by you is provided.

Creating a JSON Web Token

JSON Web Tokens are defined by an open standard and can be generated by various third party libraries in many different programming languages. A JWT is a JSON object containing metadata, such as the expiration time of your request, and is signed with an RSA private key known only to you. Bitmovin holds the corresponding public part of your RSA key pair and uses it to verify the token's authenticity. To learn more about JSON Web Tokens, refer to this document.

By default, a signed Stream is valid for 5 hours. If you want to change that value you can set the exp claim in the JWT to a custom value. It is recommended to choose a duration longer than your Stream's length; otherwise, the signed URL will expire prematurely, preventing you from viewing the complete video.

/**
 * This example uses jose:
 * https://github.com/panva/jose
 */
import * as jose from "jose";

/**
 * This block represents the base64 decoded private key used for signing the JWT token.
 */
const privateKey = `-----BEGIN PRIVATE KEY-----
CONTENTS-OF-YOUR-PRIVATE-KEY-HERE
-----END PRIVATE KEY-----`;

/**
 * (Optional) Specifies the expiration time in a custom format, for instance, '2h' signifies a two-hour duration.
 */
const customExpirationTime = '2h';

/**
 * Algorithm used for signing the JWT, set to RSA256.
 */
const alg = 'RS256';

/**
 * Converts the key string to a format required by the JOSE library.
 */
const key = await jose.importPKCS8(privateKey, alg);

/**
 * Generates a base64 encoded JWT token using the provided private key and specified settings.
 */
const jwt = await new jose.SignJWT({})
  .setProtectedHeader({ alg })
  .setExpirationTime(customExpirationTime)
  .sign(key);

# This example uses python-jose / Cryptography:
# pip install 'python-jose[cryptography]'
from jose import jwt
import time

private_key = """
-----BEGIN PRIVATE KEY-----
CONTENTS-OF-YOUR-PRIVATE-KEY-HERE
-----END PRIVATE KEY-----
"""

customExpirationTime = 2 * 60 * 60  # (Optional) Specifies the expiration time in seconds

token = {
    'exp': int(time.time()) + customExpirationTime
}

json_web_token = jwt.encode(
    token, private_key, algorithm="RS256")

print(json_web_token)

/*
 * This example uses Auth0 Java JWT.
 *
 * For Gradle, add this dependency in build.gradle:
 * implementation 'com.auth0:java-jwt:4.4.0'
 *
 * For Maven, add this dependency in your pom.xml:
 * <dependency>
 *    <groupId>com.auth0</groupId>
 *    <artifactId>java-jwt</artifactId>
 *    <version>4.4.0</version>
 * </dependency>
 *
 * Ensure to use the latest version available.
 */

import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import java.security.KeyFactory
import java.security.interfaces.RSAPrivateKey
import java.security.spec.PKCS8EncodedKeySpec
import java.util.*

val privateKeyString = """
-----BEGIN PRIVATE KEY-----
CONTENTS-OF-YOUR-PRIVATE-KEY-HERE
-----END PRIVATE KEY-----
""".trimIndent()

fun main(args: Array<String>) {
    try {
        val privateKey = getPrivateKeyFromString(privateKeyString)
        val twoHoursInMillis = 2 * 60 * 60 * 1000 // (Optional) Specifies the expiration time in seconds
        val expirationTime = Date(System.currentTimeMillis() + twoHoursInMillis)

        val algorithm = Algorithm.RSA256(null, privateKey)
        val token = JWT.create()
            .withExpiresAt(expirationTime)
            .sign(algorithm)

        println(token)
    } catch (exception: Exception) {
        // Invalid Signing configuration / Couldn't convert Claims.
    }
}

fun getPrivateKeyFromString(key: String): RSAPrivateKey {
    val cleanedPrivateKey = key
        .replace("-----BEGIN PRIVATE KEY-----", "")
        .replace("-----END PRIVATE KEY-----", "")
        .replace("\\s".toRegex(), "")

    val keySpec = PKCS8EncodedKeySpec(Base64.getDecoder().decode(cleanedPrivateKey))
    val keyFactory = KeyFactory.getInstance("RSA")
    return keyFactory.generatePrivate(keySpec) as RSAPrivateKey
}

Add Token to Streams web component

Once a token has been generated, you must add it to the Streams web component by specifying the token parameter as follows:

<bitmovin-stream stream-id="your-stream-id" token="your-token-value"/>

Upon validation of the token, permission is granted for Stream access, initiating the playback.

Available Endpoints

Streams provides the following endpoints for managing signing keys:

POST /signing-keys: Creates a new signing key pair and returns the private key in base64 encoding. Please note that there is a limit of 2 active signing keys per organization.

GET /signing-keys: Retrieves your public signing key ids.

DELETE /signing-keys/{keyId}: Deletes an existing signing key by id.

Key Rotation

In case you forgot or accidentally exposed your private key, you can rotate your signing key by following these steps:

  1. Create a new signing key by visiting the /signing-keys POST endpoint. (Limited to 2 keys)
  2. Ensure that your new private key is set up and ready for use on your side.
  3. When you are confident that everything is ready with your new key, you can delete the old key via the /signing-keys/{keyId} DELETE endpoint.

Limitations

Signed Streams is only currently supported via the Bitmovin CDN.