Announcing a new way to generate signed URL tokens

Hi all,

We’ve received a lot of feedback around signed URL generation. One thing we heard repeatedly is that “it is hard to figure out the process of setting up keys and creating signed tokens ourself.” Based on the feedback, we built a new /token endpoint that can be used to quickly test the signed URL feature. If you are generating <10,000 signed tokens a day, this service can be used in production.

For high volume use cases, we continue to suggest creating signed tokens on your backend without needing to call the Stream /token endpoint each time. This is option 2 in the docs.

Finally, we’ve overhauled the signed URL documentation to make both of these options much easier to understand and use. Please checkout the revised signed URL docs and share your feedback. You may need to clear your browser cache to see the most upto date docs.

6 Likes

Thanks @zaid
Would it be possible to share a Node approach to creating signed URLs? The Cloudflare Worker approach shown seems to rely on some browser-specific methods. There are many steps in your approach, but if you follow a well-known approach documented somewhere else I would also be satisfied with that, so I can accomplish the same on a Node server

@simon50 — the WebCrypto APIs used in the example Cloudflare Worker are supported in node.js, since v15:

https://nodejs.org/api/webcrypto.html#web-crypto-api

Alternatively, if you’re running an older version of node.js you can use Node’s Crypto APIs, which provide equivalent methods:

https://nodejs.org/api/crypto.html

We’re working on providing more code examples of working with Signed URLs — in the meantime, if you build an example, we welcome submissions to our docs, or code examples:

1 Like

Hi there! Thanks a lot for the documentation. I’m struggling a bit with creating the token myself using the second option. I copied the code in your example using my keys and video ID, but I get a very long token, and a “401 unauthorized failed to fetch verification key” error, when trying to fetch the video.

Can you help with that? Also, do you have an example in Ruby?

Many thanks. Other than that your docs have proven very helpful and easy to follow.

Hi @irvinebroque

Here is a code example for Node. I have not tested your code example for Cloudflare Worker in a browser, but it is definitely not easily translated into Node. Hence the following example for Node works, but I think you could provide a more simple approach in a future version.

  1. First, you need 2 string values: “keyId” and “pem”. You can store them safely, so you don’t have execute this method for every token generation
// One-time method to obtain keyId and pem for use in generateSignedToken()
// Informed by https://community.cloudflare.com/t/generating-signed-tokens-in-node/328114/3
import axios, { AxiosError, AxiosRequestConfig } from 'axios'
const jwkToPem = require('jwk-to-pem')

const obtainKeyIdAndPemForTokenGeneration = (async (params: {
  cloudflareAccountId: string
  cloudflareAccessToken: string
}): Promise<{ keyId: string, pem: string }> => {
  const { cloudflareAccountId, cloudflareAccessToken } = params

  // First, Obtain keys from Cloudflare
  const obtainKeyConfig: AxiosRequestConfig<unknown> = {
    method: 'post',
    url: `https://api.cloudflare.com/client/v4/accounts/${ cloudflareAccountId }/stream/keys`,
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${ cloudflareAccessToken }`
    }
  }

  try {
    const obtainKeyResponse = await axios(obtainKeyConfig)
    const key: { id: string, pem: string, jwk: string, created: string } = obtainKeyResponse.data?.result

    // Decode JWK from base64
    const jwkDecoded = JSON.parse(Buffer.from(key.jwk, 'base64').toString('latin1'))

    // construct pem
    const pem = jwkToPem(jwkDecoded, { private: true })

    // Return keyId and constructed pem
    return {
      keyId: key.id,
      pem
    }
  } catch (error: unknown) {
    const streamError = (error as AxiosError).response
    console.log({
      message: 'Error obtaining keys from Cloudflare Stream',
      error: streamError || error
    })
  }
})

Save the keyId and pem, so you can use them for generating tokens…

  1. Now, when you want to generate a token, use this method:
// Use obtainKeyIdAndPemForTokenGeneration() to get pem and keyId
// Informed by https://community.cloudflare.com/t/generating-signed-tokens-in-node/328114/3
const jwt = require('jsonwebtoken')

const generateSignedToken = (async (params: {
  videoId: string,
  includeDownloadLink: boolean
  pem: string
  keyId: string
}): Promise<{
  allowsDownload: boolean,
  expires: number,
  signedToken: string
}> => {
  const { videoId, includeDownloadLink, pem, keyId } = params

  const expiresTimeInSeconds = (2 * 60 * 60) // limit token lifetime to the next 2 hours

  // const encoder = new TextEncoder()
  const expiresIn = Math.floor(Date.now() / 1000) + expiresTimeInSeconds
  const data = {
    'sub': videoId,
    'kid': keyId,
    'exp': expiresIn,
    /* 'accessRules': [
      {
        'type': 'ip.geoip.country',
        'action': 'allow',
        'country': [
          'GB'
        ]
      },
      {
        'type': 'any',
        'action': 'block'
      }
    ] */
  }

  const signedToken = jwt.sign(data, pem, { header: { 'kid': keyId }, algorithm: 'RS256' })

  return {
    allowsDownload: includeDownloadLink,
    expires: expiresIn,
    signedToken
  }
})

````