Office 365 CDN – Some Notes and Sample Scripts

The Office 365 CDN (Content Delivery Networks) may be activated to host SharePoint Online files in a more globally accessible manner. The general premise behind this is that static assets can be served to users from a location more local to them than the data centre in which the Office 365 tenant is located.

I won’t go into the real benefits of this beyond to say that my limited testing at this point leads me to believe that the performance impact of using a CDN will be negligible for the vast majority of users/organisations. This is because the volume of data which can be served via the CDN is not a significant proportion of the data impacting page load speed.

Regardless, the documentation around how to get started with the Office 365 CDN is decent. A good place to start is this link.

Private CDN with auto-rewrite
Private CDN with auto-rewrite. Image credit to Microsoft (https://dev.office.com/blogs/general-availability-of-office-365-cdn)

A couple of gotchas I’ve noticed

  • Fetching an image rendition using the width query string parameter does NOT correctly return the image rendition as configured. It simply scales the image to the specified width (i.e. no cropping or positioning is performed).
  • If all users are located in the same region as the Office 365 tenant, turning on the CDN may reduce performance due to CDN priming (replication of files to the CDN) and will complicate updates to files which are replicated (e.g. JavaScript in the Style Library).
  • Search web parts must be configured for ‘Loading Behaviour’ – ‘Sync option: Issue query from the server’ in order for the auto rewrite of CDN hosted files to occur. This is true for display templates as well as the value of the PublishingImage managed property

Office 365 CDN PowerShell Samples

I’ve got some sample PowerShell below showing how to activate the Office 365 CDN (there’s private and public, you can use either or both) and associate origins with it (an origin is a document library which will be replicated to the CDN).

I’ve also got a simple sample of how to remove all origins as there is not a single cmdlet for this. It is worth noting that although an enabled CDN with no origins is functionally identical to a disabled CDN (i.e. no files are being replicated) they are not the same from a configuration perspective.

Please note that these are just sample scripts and have not been parameterised as you may require.

Calling the PowerShell functions:

$cdnType = "Private" # Private or Public
$serverRelSiteCollectionUrl = "/sites/mysite" # site collection URL or * for all site collections

Authenticate-PowerShell

Set-CdnConfiguration $serverRelSiteCollectionUrl $cdnType

#Remove-CdnConfiguration $cdnType # This removes all origins but the CDN is still enabled
#Set-SPOTenantCdnEnabled -CdnType $cdnType -Enable $false # This disables the CDN

The PowerShell functions:

Function Authenticate-PowerShell() {
	[string]$tenantUrl = "https://TENANT-admin.sharepoint.com"
	[string]$adminUsername = "USER@TENANT.onmicrosoft.com"
	[string]$adminPassword = "PASSWORD"

	# Ensure module is loaded
	if ((Get-Module Microsoft.Online.SharePoint.PowerShell).Count -eq 0) {
		Import-Module Microsoft.Online.SharePoint.PowerShell -DisableNameChecking
	}

	$secureAdminPassword = $(convertto-securestring $adminPassword -asplaintext -force)
	$cred = New-Object -TypeName System.Management.Automation.PSCredential -argumentlist $adminUsername, $secureAdminPassword
	Connect-SPOService -Url $tenantUrl -credential $cred
}

Function Set-CdnConfiguration($serverRelSiteCollectionUrl, $cdnType){
    #LogWaiting "Configuring CDN"
    #LogInfo ""

    $fileTypes = "GIF,ICO,JPEG,JPG,JS,PNG,CSS,EOT,SCG,TTF,WOFF"
    $cdnOrigins = @(
        "$serverRelSiteCollectionUrl/_catalogs/masterpage", 
        "$serverRelSiteCollectionUrl/style library",  
        "$serverRelSiteCollectionUrl/sitecollectionimages",
        "$serverRelSiteCollectionUrl/publishingimages"#,
        #"$serverRelSiteCollectionUrl/news/publishingimages"
    )

    # Enable cdn WITHOUT default origins
    $supressOutput = Set-SPOTenantCdnEnabled -CdnType $cdnType -Enable $true -NoDefaultOrigins -Confirm:$false

    # Configure cdn origins (incl ensure default origins)
    Ensure-CdnOrigin $cdnType $cdnOrigins

    # Extend list of file types
    $supressOutput = Set-SPOTenantCdnPolicy -CdnType $cdnType -PolicyType IncludeFileExtensions -PolicyValue $fileTypes

    #LogSuccess "done"

    # Print status
    Get-SPOTenantCdnOrigins -CdnType $cdnType
}

Function Ensure-CdnOrigin($cdnType, $originUrls){
  $originUrls | ForEach {
    $oUrl = $_
    try {
      #LogWaiting "Adding origin: $oUrl"
      $supressOutput = Add-SPOTenantCdnOrigin -CdnType $cdnType -OriginUrl $oUrl -Confirm:$false
    }
    catch {
      if($Error[0].Exception.ToString().Contains("The library is already registered as a CDN origin")) {
        # aleady present, do nothing
      }
      else {
        #LogError $Error[0]
        Exit
      }
    }
    #LogSuccess "done"
  }
}

Function Remove-CdnConfiguration($cdnType){
	(Get-SPOTenantCdnOrigins -CdnType $cdnType)	| ForEach { $_ | ForEach { $supress = Remove-SPOTenantCdnOrigin -CdnType $cdnType -OriginUrl $_ -Confirm:$false }}
}

Paul.

OAuth On-Behalf-Of Flow: Getting a user access token without user interaction. Includes ADAL sample.

OAuth 2.0 (and hence Azure Active Directory) provides the On-Behalf-Of flow to support obtaining a user access token for a resource with only a user access token for a different resource – and without user interaction.
This supports the scenario where a secured Web API acts as an interface to other resources (a.k.a endpoints) secured by the same identity provider and that require user context. As a practical example, a mobile client accesses some resources via a middle tier API which provides services such as data processing, caching, API simplification/optimisation, joining of datasets, etc.

The OAuth flow that achieves this is called the On-Behalf-Of flow; this makes sense as we’re facilitating the middle tier to act on behalf of the client when it accesses the resources farther down.

Using the on-behalf-of flow to access a resource via a middle tier API

Some background

Authentication with an OAuth 2.0 identity provider (such as AAD) produces JWT tokens. These tokens include information such as which claims (permissions) the user should be granted and the particular resource at which the token is valid (such as graph.microsoft.com). The OAuth 2.0 framework is specified such that a given token can only ever be valid for a single resource. This means that the token received by an endpoint (such as an Azure App Service Web API) cannot be used to directly authenticate to ‘another resource’. This is because the token’s resource will be that of the Web API and not the ‘other resource’. To see this I recommend checking out jwt.io and cracking open some tokens.

For completeness, the ‘other resource’ could be accessed using app-only authentication if it supports it, and if user context is not required (i.e. the return value will be the same regardless of the user) although this may greatly increase complexity in a multi-tenant scenario.

Configuring AAD for on-behalf-of

Before we get to the code the first hurdle is configuring AAD app registrations correctly. Initially it may be tempting to consider having both the Client and Web API layers utilise a single AAD app registration. After all, they are same holistic ‘app’ and how else can we get a user to consent to the permissions required by the Web API app when there is no interactive interface at that point? The latter point is resolved by explicitly binding the app registrations so that both are consented to as one. I mention how this is done below. By having two app registrations the flexibility of configuration is improved; we can have a Native app registration for the client and a Web API app registration for the Web API, we can have implicit flow configured for one app and not the other, and generally have granular control over configuration. Most vitally, an app registration can’t issue tokens valid for its own resource so two app registrations is a requirement.

I’ll avoid stepping though the configuration of the app registrations here as this is available elsewhere including this GitHub project. I will give a high level overview of what needs to happen.

    1. Create app registration for the Web API
      • Assign permissions to the downstream resources (e.g. Microsoft Graph, a custom Web API, etc)
      • If supporting multi-tenant authentication ensure availableToOtherTenants is set to true in the manifest
    2. Create app registration for the Client
      • Assign permissions to the app registration created above for the Web API
      • If supporting multi-tenant authentication ensure availableToOtherTenants is set to true in the manifest
    3. Associate app registrations
      • In the manifest for Web API app registration, configure knownClientApplications to reference the App ID for the app registration created for the Client. E.g. "knownClientApplications": ["9XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXc"]
        This binds the app registrations such that the Web API app registration is consented to as part of a single consent dialog displayed to a user when they authenticate to the Client app registration.
Before and after the app registrations are associated. Note how ‘Access Mobile App Backend’ is no longer present and instead is expanded to show the individual permissions required by that app.

Access Token Broker code

The following code is a .NET example of how to use the Active Directory Authentication Library (ADAL) to achieve the On-Behalf-Of flow.

Credits and further reading

This GitHub project was a very useful resource and a recommended starting point:
https://github.com/Azure-Samples/active-directory-dotnet-webapi-onbehalfof-ca

Microsoft docs OAuth 2.0 On-Behalf-Of flow
https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols-oauth-on-behalf-of

OAuth 2.0 specification
https://tools.ietf.org/html/rfc6749

Redis Token Cache example
https://blogs.msdn.microsoft.com/mrochon/2016/09/19/using-redis-as-adal-token-cache/

Vardhaman Deshpande – always helpful
http://www.vrdmn.com/

I was inspired to write this post not because this information isn’t available but because the information is hard to find if you aren’t familiar with the term “On-Behalf-Of”. Hopefully this post will be found by those of you searching for terms like “trade access token for new resource”, “change token resource”, “use access token with multiple resources/endpoints”, “access Microsoft Graph via Web API”, etc.

Paul.

Excuting JS in an RSS Viewer web part in SharePoint

Back in SharePoint 2010 the RSS Viewer web part (a.k.a RSS Aggregator web part, RSSAggregatorWebPart) supported the use of XSL templates which contained script tags. There were a few funny things you had to in order get it working but it did work. Bring on SharePoint Online and it is not possible to add a script tag to your XSL templates (I believe this ‘issue’ exists on SharePoint 2013 as well). More accurately, script tags can be added and will rendered to the page but the script will not be executed.

In this day and age the correct answer is very likely: “why are you using this web part?” or “XSL are you mad?”. Both very strong arguments, but regardless, for the record you can code (hack) your way around this issue.

Use the onerror handler of an image tag to instantiate your JS. Ensure that the img tag is rendered as the last element in your template if you plan on using the js to modify the DOM.

<img style='display:none' src='#' onerror='console.log("boo!")' />

If you are using SharePoint 2010 check this out:
https://sharepoint.stackexchange.com/questions/9720/calling-javascript-within-xsl-after-transformation-in-rss-web-part

Paul.