Token authentication with "Azure Verizon Premium CDN"

This walkthrough demonstrates how to use "Azure CDN Premium from Verizon" in front of a private blob storage container as origin.

Overview

In this system, the client's browser should securely access an asset stored in a private container in blob storage, via CDN. Secure in this context means that access to the cached asset in CDN should only be granted to authorized clients, and access from the CDN to the origin should be authenticated as well. The term 'origin' usually refers to the service 'behind' the CDN, in our case an Azure Storage Account.

To implement this, the client signs in 'somehow' to the web application, using whatever is in place (step 1 below). The web application generates a 'CDN auth token', and tells the browser to fetch the asset from CDN (step 2 below). That CDN URL contains the CDN auth token in the URL's query string. The CDN endpoint (strictly speaking the CDN's point of presence (PoP) or edge node) validates the CDN auth token, and (when allowed) either serves the cached copy of the asset, or fetches the contents from the origin (step 3 below).

Create a storage account

  • Config

    • Storage account name: cdnorigi

    • Force HTTPS

    • Enable storage account keys

Create a private container named cdnfiles

  • Inside the container, create an "Stored access policy"

    • Policy identifier: cdn

    • Permissions: [Read]

    • Start time: Yesterday

    • Expiry time: Something far in the future

  • After creating the policy, go the the container's "Shared access tokens" tab

    • Signing key: Key 1 or Key 2

    • Stored access policy == cdn

    • Click "Generate SAS token and URL"

      • The Blob SAS token looks like this: si=cdn&spr=https&sv=2021-06-08&sr=c&sig=Eod7p4YFtfp2rQX2OWT9ZJbNogCNa3Y%2FoH7O82%2F4UdI%3D

      • The Blob SAS URL looks like https://cdnorigi.blob.core.windows.net/cdnfiles?si=cdn&spr=https&sv=2021-06-08&sr=c&sig=Eod7p4YFtfp2rQX2OWT9ZJbNogCNa3Y%2FoH7O82%2F4UdI%3D

  • Upload a sample file to the container

    • After uploading the file, check that the SAS actually works, by putting the filename into the middle of the SAS URL, such as

      • https://cdnorigi.blob.core.windows.net/cdnfiles/2021-01-11-Captain-Haddock-02.jpg?si=cdn&spr=https&sv=2021-06-08&sr=c&sig=Eod7p4YFtfp2rQX2OWT9ZJbNogCNa3Y%2FoH7O82%2F4UdI%3D

Create a new "Front Door and CDN profiles" offer via the Azure Portal

  • Select "Explore other offerings" (instead of the default "Azure Front Door")

  • In "Choose other offerings", pick "Azure CDN Premium from Verizon"

  • In Profile Details

    • Name: cdnchgeuer

    • Region: Global, Pricing Tier: Premium Verizon

  • Tick the "Create a new CDN endpoint" box

    • CDN Endpoint name: cdnchgeuer20230119

      • which means our public hostname will be https://cdnchgeuer20230119.azureedge.net, unless we later enable a custom domain name.

    • Origin type: "Storage"

    • Origin hostname: Pick your storage account, in my case cdnorigi.blob.core.windows.net

  • Click Create, and take a cup of coffee.

    • What now happens is that Azure in the back calls out to APIs of Edgecast/Verizon to provision a CDN profile for you. That can take a while, because Verizon under the hood will roll out your chosen hostname globally to all the various edge nodes (also called 'points of presence')

  • Configure the endpoint: Once provisioning is finished, in the resource group you should see the Storage account, the CDN profile, and the CDN endpoint:

In the endpoint's "Origin" settings, you might consider disabling the http port

All the rest of the fine-grained configuration happens by navigating to "Advanced Features" and clicking the "Manage" link at the top of the page:

You now are authenticated and redirected to cdn.windowsazure.com/http/token/default.aspx, which is the place where we can configure token authentication:

Configure the HTTP Large Object Token-Based Authentication

  • In the Linux shell (for example in a bash shell in WSL), we'll be using OpenSSL to generate to high-entropy cryptographic keys:

  • Run openssl rand -hex 160 twice and note down the results. This command generates hexadecimal encoded cryptographically strong random numbers, and the number is the number of bytes. 160 bytes would be 1280 bit, encoded in 320 hex characters.

  • Prefix the first value with primary and the second one with backup, so it's easier to see which key you're looking at.

primary4c5604fb8f76524cd99cf65700fd550a92f083d297c327a50cae5891f1f6fcb6965cadaaa56682e3998e376f1cb3db757348356f73a936bfdae12c4eab07e02e70d5108593a97f267cdb2c16c87c58832270a7cfd0199ecd0808169f54238be400e7950f44bce9f801395061cf741ac1f12ea2341b8887c2b6692b6074695854834047c8bff52b3a284a51290483a985f78ad0c548d98fe306c9be3cd12605b8
  
backupf876ca22ccca6cf4587137251e44490a131b8cca12ab65bbdd8ffa33edeb9862f850d2050e9d3df1b2bcc72ca5ddbf3f32145c846464ce8cfadd6f36881371f88ca6942f1a685211e3eac25cb9b79bc50aad65c7b04d754637de2ee7e8429fdc25a7556e3628899a43ca94178c6d3777229b345132cada7aef4bf7f2c68bb0b04c109ece2a301486c61d5a85b5b2eaaf55ae4e06b3198ec1c9c2606f9b33c2a0
  • You can also bump up the "Minimum Encryption Version" from V2 to V3

Configure custom rules

By clicking on the "HTTP Large" menu on the top, and selecting "Rules Engine V4.0", you come to the graphical rule editing experience.

Click the +New button, and type in a new name for the draft rule, such as v1, and click "Continue"

Create a first rule, to require token authentication

  • Click the +Rule button

  • Rule Description: Require CDN Token

  • Click the + dropdown and select Match

  • The Select Category dropdown should be URL

  • The second dropdown should be URL Path Directory Wildcard

  • The Result dropdown should be Match

  • The Value is the path in the CDN URL, something like /cdnfiles/.

  • The Relative To dropdown should be Origin

  • Enforce token auth: Next, click the indented + and select Feature

    • Select Category dropdown == Access

    • Select Feature dropdown == Token Auth

    • Enabled == yes

  • Name the token parameter:

    • Add another Feature, this time Access and Token Auth Parameter, and Enabled=yes

Finally, save our first rule.

Create a second rule, using URL Rewriting to configure access to the private storage account container, using our SAS

  • Click +Rule to add a second rule, with a rule description of Append SAS Token or similar

  • Click the + button and again select Match, this time with General / Always

    • On the indented + button, add a Feature in the URL category, with feature being URL Rewrite

  • Now we need to tell the CDN how incoming URLs should be re-written to the external origin server (Azure Blob storage in our case).

    • To know the string we need to know, temporarily click the bottom + button to add another Match, and select Origin / Customer Origin. In the Value dropdown, you should see something like this:

  • In the dropdown (marked with a 1 in the screenshot), you can see a customer origin identifier, in our case /801A0567/cdnchgeuer20230119/. Please type this text into the Source and Destination. The cdnchgeuer20230119 part in this string is the name of the endpoint which you configured earlier in the Azure portal.

  • However, the /801A0567/ looks slightly random. In practice, this is your Verizon customer ID, which you can see in the upper righthand corner of the portal (1A0567), prefixed with the 80. So in theory, you could just look at your user account info on screen, but in case that 80 prefix changes in the future, temporarily creating a customer origin rule makes it easy to lookup.

  • Now please delete the temporarily created match, by clicking the little recycle bin icon (2).

Our screen should now look like this:

  • Now that our Source and Destination textboxes have the base information, we need to extend it with a mechanism to inject our shared access signature token.

  • In the Source textbox, append the name of your container, and the regular expression (.*)\?(.*) to the content, so it looks like this: /801A0567/cdnchgeuer20230119/cdnfiles/(.*)\?(.*)

    • The request coming on to the CDN looks something like this: https://cdnchgeuer20230119.azureedge.net/cdnfiles/somefile.zip?cdntoken=xxx, i.e. the path prefix /cdnfiles/, a file (somefile.zip), and the query string containing our CDN token (?cdntoken=xxx).

    • The regular expression cracks up the path around the questionmark (\?), into the file name (somefile.zip) in the first regex capture group, and the query string (cdntoken=xxx) in the second regex capture group.

  • In the Destination text box, we concatenate the following strings

    • Our base /801A0567/cdnchgeuer20230119/

    • Our storage account container name cdnfiles/

    • Then we put the file name, which is captured in the 1st capture group, by adding $1

    • The ? to start the query string again

    • Our SAS string (si=cdn&spr=https&sv=2021-06-08&sr=c&sig=Eod7p4YFtfp2rQX2OWT9ZJbNogCNa3Y%2FoH7O82%2F4UdI%3D)

    • An additional & for additional parameters

    • The second capture group $2

    • So the overall result is /801A0567/cdnchgeuer20230119/cdnfiles/$1?si=cdn&spr=https&sv=2021-06-08&sr=c&sig=Eod7p4YFtfp2rQX2OWT9ZJbNogCNa3Y%2FoH7O82%2F4UdI%3D&$2

  • Save the second rule, and click "Lock Draft as Policy"

  • In the Raw XML window, you can also see an XML representation of the rules

<policy>
  <rules>
    <rule>
      <description>Require CDN Token</description>
      <match.url.url-path-directory.wildcard result="match" 
        value="/cdnfiles/" ignore-case="false" relative-to="origin">
        <feature.access.token-auth enabled="true"/>
        <feature.access.token-auth-parameter name="cdntoken" enabled="true"/>
      </match.url.url-path-directory.wildcard>
    </rule>
    <rule>
      <description>Append SAS Token</description>
      <match.always>
        <feature.url.url-rewrite
          source="/801A0567/cdnchgeuer20230119/cdnfiles/(.*)\?(.*)"
          destination="/801A0567/cdnchgeuer20230119/cdnfiles/$1?si=cdn&amp;spr=https&amp;sv=2021-06-08&amp;sr=c&amp;sig=Eod7p4YFtfp2rQX2OWT9ZJbNogCNa3Y%2FoH7O82%2F4UdI%3D&amp;$2"/>
      </match.always>
    </rule>
  </rules>
</policy>
  • Now, click the "Deploy Request" Button, select the "Production" environment, type in a deployment message (like "Deploying v1"), and "Create Deploy Request"

  • Once submitted, the Verizon system will validate the rules, after a couple of seconds, you should be seeing an "Approved" mark in the Activity log. Overall, the deployment of the rules to the various edge / PoP nodes might take a few minutes.

Create a CDN token

  • Finally, we can create a CDN token, to test our solution. We navigate back to the "HTTP Large / Token Auth" page. Further down on that page is an "Encrypt Tool" section, a small web-based UI to create a CDN token. For a simple test, we need two properties in the token, an expiration date (ec_expire) and a part of the URL we're granting access to (ec_url_allow).

  • The ec_expire header must be an epoch value, an integer which you can create using web sites such as unixtimestamp.com or epochconverter.com. For example, the timestamp "Wed Dec 31 2025 23:59:59 UTC" corresponds to the epoch value 1767225599.

  • The ec_url_ allow can contain values such as /cdnfiles.

  • Encrypting these two parameters under my primary key gives me a generated token of is9St4wZ7gTK8tuPXxKQtcmh7Bi3aMJ8b-yHxvJgyDdcJOyrePUmVIj9HXVFl1WbLWRDiuS-3RaeEgpg-JIsJT9ChNKfqis

In your solution, you (of course) won't visit the Verizon portal to generate a CDN token. You will be using your web application to (protected) generate web links pointing to CDN. So in your web solution, you need to generate these CDN tokens programatically, you certainly want to make these short-lived, limit them to a specific client, and use all the other good features.

On the .NET side, I created a small library Azure-Samples/edgecast-cdn-token-fsharp: A .NET sample to create tokens for Edgecast Azure CDN which can help you generating such tokens. In a C# solution for example, you could generate tokens like this:

using System;
using EdgecastCryptoExtensions;

var key = "primary4c5604fb8f76524cd99cf65700fd550a92f083d297c327a50cae5891f1f6fcb6965cadaaa56682e3998e376f1cb3db757348356f73a936bfdae12c4eab07e02e70d5108593a97f267cdb2c16c87c58832270a7cfd0199ecd0808169f54238be400e7950f44bce9f801395061cf741ac1f12ea2341b8887c2b6692b6074695854834047c8bff52b3a284a51290483a985f78ad0c548d98fe306c9be3cd12605b8";

var token1 =
    EdgecastCrypto
        .createTokenValidUntil(
            new DateTime(
                year: 2025, month: 12, day: 31, 
                hour: 23, minute: 59, second: 59))
        .AddAllowedUrl("/cdnfiles/")
        .Encrypt(key);

var token2 =
    EdgecastCrypto
        .createTokenValidFor(TimeSpan.FromDays(365.0))
        //.AddAllowedCountry("DE")
        // more extension methods available
        .AddAllowedUrl("/cdnfiles/")
        .Encrypt(key);

Console.Out.WriteLine($"Token1: {token1}");
// Token1: sU5Hu8WjfvysnYVmy6i4nsdNbQGrlnc7snQN075VgHJAj_XVnehjA - xGohmvDwCdJHGFirgBVoAoUmSdrtajA76KwB_5Hsw3

Console.Out.WriteLine($"Token2: {token2}");
// Token2: sU5Hu8WjfvysnYVmy6i4nsdNbQGrlnc7tHYI0bpZj3lAj_XVnehjA - xGohmvDwCdJHGFirgBVoD1w7xarONEKKhIGWp9nrT2

Accessing a file.

With all that information, we can now hit the CDN endpoint and access a file in our private blob storage container.

As a quick reminder, the direct URL to hit our origin server (blob storage) contained the shared access signature:

https://cdnorigi.blob.core.windows.net/cdnfiles/2021-01-11-Captain-Haddock-02.jpg?si=cdn&spr=https&sv=2021-06-08&sr=c&sig=Eod7p4YFtfp2rQX2OWT9ZJbNogCNa3Y%2FoH7O82%2F4UdI%3D

Instead of the SAS, the CDN URL contains our cdntoken:

https://cdnchgeuer20230119.azureedge.net/cdnfiles/2021-01-11-Captain-Haddock-02.jpg?cdntoken=is9St4wZ7gTK8tuPXxKQtcmh7Bi3aMJ8b-yHxvJgyDdcJOyrePUmVIj9HXVFl1WbLWRDiuS-3RaeEgpg-JIsJT9ChNKfqis

When we hit the CDN without token, or a modified one, we get a 403 - Forbidden page.

Otherwise, you can enjoy this beautiful image:

Last updated