Use dual-token authentication

To enforce dual-token authentication when viewers access a streaming resource, you configure separate routes. A route is a configuration that matches requests for Media CDN and directs HTTP traffic to an origin. Media CDN supports routes for either HTTP live streaming (HLS) or Dynamic Adaptive Streaming over HTTP (DASH) resources. For more information, see Advanced routing.

For HLS streams, Media CDN supports both cookie-based and cookieless dual-token authentication. For DASH streams, Media CDN supports only cookie-based dual-token authentication.

This page describes how to configure Media CDN routes to help protect content using dual-token authentication.

Before you begin

Do the following:

  1. Choose which signature method you're using for short-duration tokens: Ed25519 signatures or symmetric-key hash-based message authentication codes (HMACs).

    You can turn on symmetric HMAC signing algorithms only for routes configured to generate new long-duration tokens. For optimal performance and security, we recommend that you use Ed25519 signatures, and use symmetric-key HMACs when necessary for compatibility with other CDNs.

    The EdgeCacheKeyset that you configure for verifying signed request tokens must include the correct keys for the signature algorithm that you choose. The following table describes each of the signature algorithms and their required keys.

    signatureAlgorithm Required keys in keyset
    Ed25519 publicKeys
    HMAC-SHA1 validationSharedKeys
    HMAC-SHA256 validationSharedKeys
  2. Tokens must be signed or verified with keys in EdgeCacheKeysets. As a best practice, make two separate keysets, one each for your short-duration tokens and long-duration tokens.

    If you are using DASH and using dynamic multimedia presentation description (MPD) files, you must use the same keyset for both the long and the short tokens.

  3. Choose one of the following token formats for the long-duration tokens:

    • Cookies
    • URI query parameters

Set up short-duration tokens

Do the following:

Ed25519 signature

  1. Run the following command to generate a private key:

    openssl genpkey -algorithm ed25519 -outform PEM -out SSL_KEY_NAME.private.key
    

    Replace SSL_KEY_NAME with the key name that you want.

  2. Run the following command to generate a public key from the private key you generated:

    openssl pkey -outform DER -pubout -in SSL_KEY_NAME.private.key |\
    tail -c +13 |\
    python3 -c "import base64, sys; print(('%s' % base64.urlsafe_b64encode(sys.stdin.buffer.read()))[2:-1])"
    

    For more information, see Create keys.

  3. Create a new keyset with a single public key by doing the following:

    Console

    1. Go to the Media CDN page in the Google Cloud console.
      Go to Media CDN
    2. Click the Keysets tab.
    3. Click Create keyset.
    4. In the Name field, enter keyset name—for example, prod-vod-keyset.
    5. Optional: Add a description for your keyset.
    6. Optional: Add one or more labels for your keyset.
    7. In the ID field, enter an alpha-numeric ID, such as first-key.
    8. In the Value enter your generated Ed25519 public key.
    9. Click Done.

    gcloud CLI

    gcloud edge-cache keysets create SHORT_KEYSET_NAME \
        --public-key='id=SSL_PUBLIC_KEY_NAME,value=SSL_PUBLIC_KEY_VALUE'
    

    Replace the following:

    • SHORT_KEYSET_NAME: The keyset name that you want
    • SSL_PUBLIC_KEY_NAME: The name of your SSL public key
    • SSL_PUBLIC_KEY_VALUE: The value of your SSL public key

    The output is similar to the following:

    [Keyset "prod-vod-keyset" created]
    

    You can review the keys associated with a keyset by inspecting (describing) it by name:

    gcloud edge-cache keysets describe prod-vod-keyset
    

    The output is similar to the following:

    name: prod-vod-keyset
    description: "Keyset for prod.example.com"
    publicKeys:
    - id: "key-20200918"
      value: "DThVLjhAKm3VYOvLBAwFZ5XbjVyF98Ias8NZU0WEM9w"
    - id: "key-20200808"
      value: "Lw7LDSaDUrbDdqpPA6JEmMF5BA5GPtd7sAjvsnh7uDA="
    

    Terraform

    resource "google_network_services_edge_cache_keyset" "default" {
      name        = "prod-vod-keyset"
      description = "Keyset for prod.example.com"
      public_key {
        id    = "key-20200918"
        value = "FHsTyFHNmvNpw4o7-rp-M1yqMyBF8vXSBRkZtkQ0RKY" # Update Ed25519 public key
      }
      public_key {
        id    = "key-20200808"
        value = "Lw7LDSaDUrbDdqpPA6JEmMF5BA5GPtd7sAjvsnh7uDA=" # Update Ed25519 public key
      }
    }

Symmetric-key HMAC

  1. If you haven't used Secret Manager before, configure Secret Manager.

  2. Use Secret Manager to create a secret.

  3. Use Secret Manager to add a secret version.

    Ensure that the secret version that you add is in binary format.

  4. Grant the Secret Manager Access role (roles/secretmanager.secretAccessor) to the Media CDN service account:

    Console

    1. In the Google Cloud console, go to the Secret Manager page.

      Go to Secret Manager

    2. On the Secret Manager page, select the checkbox next to the name of the secret.
    3. If it is not already open, click Show Info Panel to open the panel.
    4. In the info panel, click Add Principal.
    5. In the New principals field, enter the Media CDN service account:
      service-PROJECT_NUMBER@gcp-sa-mediaedgefill.iam.gserviceaccount.com

      Replace PROJECT_NUMBER with your project number.

    6. In the Select a role list, choose Secret Manager, and then choose Secret Manager Secret Accessor.

    gcloud

      gcloud secrets add-iam-policy-binding projects/PROJECT_NUMBER/secrets/SECRET_ID \
          --member="serviceAccount:service-PROJECT_NUMBER@gcp-sa-mediaedgefill.iam.gserviceaccount.com" \
          --role="roles/secretmanager.secretAccessor"
      

    Replace the following:

    • PROJECT_NUMBER: your project number
    • SECRET_ID: the ID of the secret
  5. Access your secret versions. Copy the secret paths, including version numbers.

  6. Include the shared secrets in a Media CDN keyset:

    gcloud edge-cache keysets create SHORT_KEYSET_NAME \
      --validation-shared-key='secret_version=projects/PROJECT_NUMBER/secrets/SECRET_ID/versions/KEY_VERSION '
    

    Replace the following:

    • SHORT_KEYSET_NAME: the name of the keyset
    • PROJECT_NUMBER: your project ID
    • SECRET_ID: the ID of the secret
    • KEY_VERSION: the key version to use

    The output is similar to the following:

    [Keyset "SHORT_KEYSET_NAME" created]
    

Set up long-duration tokens

Google-managed keys are scoped by keyset. That means that two different keysets have different Google-managed keys. Google-managed keys are rotated regularly.

To create and set up a long-duration token, do one of the following:

  • Create a Google-managed signing key for long-duration tokens:

    gcloud edge-cache keysets create LONG_KEYSET_NAME \
        --public-key='id=google-managed-key,managed=true'
    

    Replace LONG_KEYSET_NAME with a key name.

  • Modify an existing keyset:

    1. Export your keyset to a .YAML file:

      gcloud edge-cache keysets export LONG_KEYSET_NAME \
          --destination=prod-vod-keyset-long.yaml
      
    2. In a text editor or in your configuration management tooling, edit your keyset configuration to add a Google-managed file so that it looks similar to the following:

      name: projects/my-project/locations/global/edgeCacheKeysets/LONG_KEYSET_NAME
      publicKeys:
      - id: some-key
        value: MC4CAQAwBQYDK2VwBCIEINV2iYugIWBuvGBJUQ_Ab69E4v4zcVqvgYHw-iZxGzcd
      - id: google-managed-key
        managed: true
      
    3. Import the edited keyset:

      gcloud edge-cache keysets import LONG_KEYSET_NAME \
          --source=prod-vod-keyset-long.yaml
      

      The output is similar to the following:

      [imported LONG_KEYSET_NAME]
      

You can include additional public keys in the long-duration token keyset, with the following limitations:

  • Three public keys maximum
  • One Google-managed key maximum

In practice, this means that you can have two user-managed keys and one Google-managed key in a long-duration token keyset. Media CDN always uses the Google-managed key to generate tokens. The user-managed keys can be used only for verification.

Including additional public keys is useful for allowing your player application to access media playlists and media segments by using signed requests generated by your own private keys.

Create or export a configuration file

This section shows how to export or create a Media CDN configuration file, creating multiple routes. A route lets you optimize behavior based on the type of content, client attributes, and your freshness requirements. In the following examples, we use routes to configure token exchange for each part of a media request.

For more information on routes, see Advanced Routing.

If you have an existing Media CDN configuration, export your existing configuration and apply edits to the exported file. Otherwise, create a new file.

Export configuration

Export an existing configuration:

gcloud edge-cache services export SERVICE_NAME \
  --destination=FILENAME.yaml

Replace the following:

  • SERVICE_NAME: Your service's name
  • FILENAME : The filename that you want

The output is similar to the following:

Exported [projects/my-project/locations/global/edgeCacheServices/SERVICE_NAME] to 'FILENAME.yaml'.

The content of the exported configuration file is similar to the following:

name: SERVICE_NAME
routing:
  hostRules:
  - hosts:
    - DOMAIN_NAME
    pathMatcher: routes
  pathMatchers:
  - name: routes
    routeRules:
    - priority: 1
      matchRules:
      - prefixMatch: /
      origin: ORIGIN_NAME
      routeAction:
        cdnPolicy:
          cacheMode: CACHE_ALL_STATIC
          defaultTtl: 3600s

In this example:

  • SERVICE_NAME: Your service's name
  • DOMAIN_NAME: Your service's domain name
  • ORIGIN_NAME: The origin's name

For the following sections, apply edits to the configuration file you downloaded, FILENAME.yaml.

Make a configuration

Do the following:

  1. Create a local file, FILENAME.yaml.

    Replace FILENAME with the filename that you want.

  2. Enter the following content into the file:

    name: SERVICE_NAME
    routing:
      hostRules:
      - hosts:
        - DOMAIN_NAME
        pathMatcher: routes
    

    Replace the following:

    • SERVICE_NAME: Your service's name
    • DOMAIN_NAME: Your service's domain name

    For the following sections, apply edits to the configuration file you created, FILENAME.yaml.

Edit the configuration file

The following section shows how to edit the Media CDN configuration file for dual-token authentication.

Configure the primary manifest route to require short-duration tokens

Turn on token authentication on a route by specifying a signedTokensOptions configuration in the route's Media CDN policy.

Edit the configuration file as follows:

Ed25519 signature

  pathMatchers:
    - name: "ROUTE_NAME"
      routeRules:
      - priority: 1
        description: "ROUTE_DESCRIPTION"
        origin: "ORIGIN_NAME"
        matchRules:
        - pathTemplateMatch: "/MANIFEST_OR_PLAYLIST"
        routeAction:
          cdnPolicy:
            cacheMode: CACHE_ALL_STATIC
            signedRequestMode: REQUIRE_TOKENS
            signedRequestKeyset: SHORT_KEYSET_NAME
            signedRequestMaximumExpirationTtl: SIGNED_REQUEST_MAXIMUM_EXPIRATION_TIME
            signedTokenOptions:
              tokenQueryParameter: SHORT_TOKEN_NAME

Replace the following:

  • ROUTE_NAME: The name of the route
  • ROUTE_DESCRIPTION: The description of the route
  • ORIGIN_NAME: The origin name
  • MANIFEST_OR_PLAYLIST: The name of the HLS primary playlist (.m3u8) or DASH manifest (.mpd) file
  • SHORT_KEYSET_NAME: The keyset name to use for short-duration tokens
  • SIGNED_REQUEST_MAXIMUM_EXPIRATION_TIME: The expiration time for signed requests. For example, 600s. For more information, see signedRequestMaximumExpirationTtl.
  • Optional: SHORT_TOKEN_NAME: The query parameter in which to find the short token. The default value is edge-cache-token. For more information, see SignedTokenOptions.

Symmetric-key HMAC

  pathMatchers:
    - name: "ROUTE_NAME"
      routeRules:
      - priority: 1
        description: "ROUTE_DESCRIPTION"
        origin: "ORIGIN_NAME"
        matchRules:
        - pathTemplateMatch: "/MANIFEST_OR_PLAYLIST"
        routeAction:
          cdnPolicy:
            cacheMode: CACHE_ALL_STATIC
            signedRequestMode: REQUIRE_TOKENS
            signedRequestKeyset: SHORT_KEYSET_NAME
            signedRequestMaximumExpirationTtl: SIGNED_REQUEST_MAXIMUM_EXPIRATION_TIME
            signedTokenOptions:
              tokenQueryParameter: SHORT_TOKEN_NAME
              allowedSignatureAlgorithms:
               - HMAC_SHA_256

Replace the following:

  • ROUTE_NAME: The name of the route
  • ROUTE_DESCRIPTION: The description of the route
  • ORIGIN_NAME: The origin name
  • MANIFEST_OR_PLAYLIST: The names or path templates for the HLS primary playlists (.m3u8), DASH manifest (.mpd) files, or both. For more information about path matching, see Path matching.
  • SHORT_KEYSET_NAME: The keyset name to use for short-duration tokens
  • SIGNED_REQUEST_MAXIMUM_EXPIRATION_TIME: The maximum expiration time for signed requests. For example, 600s. For more information, see signedRequestMaximumExpirationTtl.
  • Optional: SHORT_TOKEN_NAME: The query parameter in which to find the short token. The default value is edge-cache-token. For more information, see SignedTokenOptions.

Modify the primary manifest route to generate long-duration tokens

Configure Media CDN to generate long-duration tokens on the primary manifest route.

Append your configuration file, by using either cookies or URL query parameters:

Cookies

          addSignatures:
            actions:
              - GENERATE_COOKIE
            keyset: LONG_KEYSET_NAME
            tokenTtl: TOKEN_EXPIRATION_TIME
            copiedParameters:
              - PathGlobs
              - SessionID

Replace the following:

  • LONG_KEYSET_NAME: The name of your long-duration token keyset.

  • TOKEN_EXPIRATION_TIME: The expiration time of the long-duration token. For example, 86400s for a one day expiration time.

This code example implements the following changes:

  • addSignatures.actions: GENERATE_COOKIE: Configures Media CDN to return an Edge-Cache-Cookie with the primary manifest response.

  • copiedParameters.PathGlobs: configures Media CDN to copy the PathGlobs from the short-duration token to the long-duration token. To use dual-token authentication, you must use one of copiedParameters.PathGlobs or copiedParameters.URLPrefix.

    For more information, see copiedParameters.

  • Optional: copiedParameters.SessionID: configures Media CDN to copy the SessionID from the short-duration token to the long-duration token.

When the GENERATE_COOKIE action is applied, Media CDN returns a Set-Cookie header similar to the following with the primary manifest response:

Set-Cookie: Edge-Cache-Cookie=PathGlobs=PATHS~SessionID=SESSION_ID~Expires=EXPIRATION~_GO=Generated~Signature=SIGNATURE

URL query parameters

          addSignatures:
            actions:
              - GENERATE_TOKEN_HLS_COOKIELESS
            keyset: LONG_KEYSET_NAME
            tokenTtl: TOKEN_EXPIRATION_TIME
            copiedParameters:
              - PathGlobs
              - SessionID
            tokenQueryParameter: LONG_TOKEN_NAME

Replace the following:

  • LONG_KEYSET_NAME: The name of your long-duration keyset.

  • TOKEN_EXPIRATION_TIME: The expiration time of the long-duration token. For example, 86400s for a one day expiration time.

This code example implements the following changes:

  • addSignatures.actions: GENERATE_TOKEN_HLS_COOKIELESS: Configures Media CDN to manipulate the HLS primary manifest by appending a long-duration token to each URI present.

  • copiedParameters.PathGlobs: configures Media CDN to copy the PathGlobs from the short-duration token to the long-duration token. To use dual-token authentication, you must use one of copiedParameters.PathGlobs or copiedParameters.URLPrefix.

    For more information, see copiedParameters.

  • Optional: copiedParameters.SessionID: configures Media CDN to copy the SessionID from the short-duration token to the long-duration token.

  • Optional: LONG_TOKEN_NAME: The query parameter in which to put the generated long token. The default value is edge-cache-token. For more information, see tokenQueryParameter.

The following manifest file shows the GENERATE_TOKEN_HLS_COOKIELESS action applied:

#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1000000
http://example.com/HLS_PRIMARY_PLAYLIST.m3u8?LONG_TOKEN_NAME=PathGlobs=PATHS~SessionID=SESSION_ID~Expires=EXPIRATION~_GO=Generated~Signature=SIGNATURE

Media CDN generates and stores the long-duration token in the LONG_TOKEN_NAME query parameter after verifying the short-duration token.

Configure subsequent routes

Configure subsequent routes for long-duration tokens. For cookies, you add an additional route to require long-duration tokens.

Cookies

Configure the media playlist and segments to require long-duration cookies

Edit the configuration file as follows:

      - priority: 2
        description: "SEGMENTS_ROUTE_DESCRIPTION"
        origin: "ORIGIN_NAME"
        matchRules:
        - pathTemplateMatch: "/**.m3u8" # HLS media playlists
        - pathTemplateMatch: "/**.ts" # HLS segments
        - pathTemplateMatch: "/**.m4s" # DASH / CMAF segments
        routeAction:
          cdnPolicy:
            cacheMode: CACHE_ALL_STATIC
            signedRequestMode: REQUIRE_TOKENS
            signedRequestKeyset: LONG_KEYSET_NAME

Replace SEGMENTS_ROUTE_DESCRIPTION with a description of the route.

This code example implements the following changes:

  • priority: 2: Higher values indicate lower priority. Because the route for your media playlists and media segments matches any filename ending in .m3u8, the priority for the route must be lower than the route you previously created for the primary manifest.
  • signedRequestMode: REQUIRE_TOKENS: Enforces tokens for media playlists and media segments.

    If you are using static media presentation description (MPD) files, the long and short keysets may be different. If you are using dynamic MPD files, then the long and short keysets must be the same.

  • signedRequestKeyset: LONG_KEYSET_NAME: Media CDN uses the long-duration keyset to validate the long-duration cookie supplied by a user agent for media playlist and media segment requests.

URL query parameters

For URL query parameters, you add two route configurations to do the following:

  • Configure the HLS media manifest to propagate long-duration tokens.
  • Configure the segment routes to require long-duration tokens.

Configure the HLS media manifest routes to propagate long-duration tokens

Edit the configuration file as follows:

     - priority: 2
       description: "PLAYLIST_ROUTE_DESCRIPTION"
       origin: "ORIGIN_NAME"
       matchRules:
       - pathTemplateMatch: "/**.m3u8" # HLS media playlists
       routeAction:
        cdnPolicy:
          cacheMode: CACHE_ALL_STATIC
          signedRequestMode: REQUIRE_TOKENS
          signedRequestKeyset: LONG_KEYSET_NAME
          addSignatures:
            actions:
              - PROPAGATE_TOKEN_HLS_COOKIELESS

Replace PLAYLIST_ROUTE_DESCRIPTION with a description of the route.

This code example implements the following changes:

  • priority: 2: Higher values indicate lower priority. Because the route for your media playlists matches any filename ending in .m3u8, the priority for the route must be lower than the route you previously created for the primary manifest.
  • signedRequestMode: REQUIRE_TOKENS: Enforces tokens for media playlists and media segments.

    If you are using static media presentation description (MPD) files, the long and short keysets may be different. If you are using dynamic MPD files, then the long and short keysets must be the same.

  • signedRequestKeyset: LONG_KEYSET_NAME: Media CDN uses the long-duration keyset to validate the long-duration cookie supplied by a user agent for media playlist and media segment requests.

  • addSignatures.actions: PROPAGATE_TOKEN_HLS_COOKIELESS: Configures Media CDN to copy the long-duration token to media segment URIs in the media playlists.

Configure the segment routes to require long-duration tokens

Edit the configuration file as follows:

     - priority: 3
       description: "SEGMENTS_ROUTE_DESCRIPTION"
       origin: "ORIGIN_NAME"
       matchRules:
       - pathTemplateMatch: "/**.ts" # HLS segments
       routeAction:
        cdnPolicy:
          cacheMode: CACHE_ALL_STATIC
          signedRequestMode: REQUIRE_TOKENS
          signedRequestKeyset: LONG_KEYSET_NAME

Replace SEGMENTS_ROUTE_DESCRIPTION with a description of the route.

This code example implements the following changes:

  • priority: 3: Higher values indicate lower priority. Route priorities must be unique.
  • signedRequestMode: REQUIRE_TOKENS: Enforces tokens for media playlists and media segments.
  • signedRequestKeyset: LONG_KEYSET_NAME: Media CDN uses the long-duration keyset to validate the long-duration signed token supplied by a user agent for media playlist and media segment requests.

Example completed file

The following code sample shows a completed configuration file:

Cookies

name: SERVICE_NAME
routing:
  hostRules:
  - hosts:
    - DOMAIN_NAME
    pathMatcher: routes
  pathMatchers:
    - name: "ROUTE_NAME"
      routeRules:
      - priority: 1
        description: "ROUTE_DESCRIPTION"
        origin: "ORIGIN_NAME"
        matchRules:
        - pathTemplateMatch: "/HLS_MASTER_PLAYLIST.m3u8" # HLS primary playlists
        - pathTemplateMatch: "/DASH_MANIFESTS.mpd" # DASH manifests
        routeAction:
          cdnPolicy:
            cacheMode: CACHE_ALL_STATIC
            signedRequestMode: REQUIRE_TOKENS
            signedRequestKeyset: SHORT_KEYSET_NAME
            signedRequestMaximumExpirationTtl: SIGNED_REQUEST_MAXIMUM_EXPIRATION_TIME
            addSignatures:
              actions:
                - GENERATE_COOKIE
              keyset: LONG_KEYSET_NAME
              tokenTtl: TOKEN_EXPIRATION_TIME
              copiedParameters:
                - PathGlobs
                - SessionID
      - priority: 2
        description: "SEGMENTS_ROUTE_DESCRIPTION"
        origin: "ORIGN_NAME"
        matchRules:
        - pathTemplateMatch: "/**.m3u8" # HLS media playlists
        - pathTemplateMatch: "/**.ts" # HLS segments
        - pathTemplateMatch: "/**.m4s" # DASH / CMAF segments
        routeAction:
          cdnPolicy:
            cacheMode: CACHE_ALL_STATIC
            signedRequestMode: REQUIRE_TOKENS
            signedRequestKeyset: LONG_KEYSET_NAME

URL query parameters

name: SERVICE_NAME
routing:
  hostRules:
  - hosts:
    - DOMAIN_NAME
    pathMatcher: routes
  pathMatchers:
    - name: "ROUTE_NAME"
      routeRules:
      - priority: 1
        description: "ROUTE_DESCRIPTION"
        origin: "ORIGIN_NAME"
        matchRules:
        - pathTemplateMatch: "/HLS_PRIMARY_PLAYLIST.m3u8" # HLS primary playlists
        routeAction:
          cdnPolicy:
            cacheMode: CACHE_ALL_STATIC
            signedRequestMode: REQUIRE_TOKENS
            signedRequestKeyset: SHORT_KEYSET_NAME
            signedRequestMaximumExpirationTtl: SIGNED_REQUEST_MAXIMUM_EXPIRATION_TIME
            signedTokenOptions:
              tokenQueryParameter: SHORT_TOKEN_NAME
            addSignatures:
              actions:
                - GENERATE_TOKEN_HLS_COOKIELESS
              keyset: LONG_KEYSET_NAME
              tokenTtl: TOKEN_EXPIRATION_TIME
              tokenQueryParameter: LONG_TOKEN_NAME
              copiedParameters:
                - PathGlobs
                - SessionID
      - priority: 2
        description: "PLAYLIST_ROUTE_DESCRIPTION"
        origin: "ORIGIN_NAME"
        matchRules:
        - pathTemplateMatch: "/**.m3u8" # HLS media playlists
        routeAction:
          cdnPolicy:
            cacheMode: CACHE_ALL_STATIC
            signedRequestMode: REQUIRE_TOKENS
            signedRequestKeyset: LONG_KEYSET_NAME
            addSignatures:
              actions:
                - PROPAGATE_TOKEN_HLS_COOKIELESS
      - priority: 3
        description: "SEGMENTS_ROUTE_DESCRIPTION"
        origin: "ORIGIN_NAME"
        matchRules:
        - pathTemplateMatch: "/**.ts" # HLS segments
        routeAction:
          cdnPolicy:
            cacheMode: CACHE_ALL_STATIC
            signedRequestMode: REQUIRE_TOKENS
            signedRequestKeyset: LONG_KEYSET_NAME

Import the edited configuration file

Apply your configuration changes and turn on dual-token authentication:

gcloud edge-cache services import SERVICE_NAME --source=FILENAME.yaml

Generate short-duration tokens at your application server

For information about generating tokens, see Generate tokens.

Apply defense-in-depth content protections

As a best practice, turn on origin authentication, as follows: