Azure Functions, JS and App-Only Updates to SharePoint Online

Have you ever, really wanted to have your JavaScript perform a RunWithElevatedPrivileges against SharePoint Online?  Do something that the current user just don't have permission to do?

Today we tackle this core problem that every SharePoint JavaScript developer has thought about at least once.  And we will do it with AzureFunctions.

1.  Register Azure Active Directory App

You can go through dev.office.com to get started - but you'll need various bits of the portal.  So do this from old portal https://manage.windowsazure.com

You need to write copy out your Client ID and Client Secret

Lets give this some App-Only Permissions

Also need to allow implicit flow, that means download the manifest, change the json (below) and upload it back.

 

2.  Create Azure Function

Go to http://functions.azure.com and sign in. 

This picture looks like an ad in my blog...

This picture looks like an ad in my blog...

Create a new function - I choose the HttpTrigger with Node template.

2.1 Add NPM modules

NodeJS modules (= C# reference libraries) are loaded via npm install (= nuget).  To access this, you need to go to

Function app settings > Go to App Settings > Tools(tipped by @crandycodes)

npm install adal-js
npm install request

The errors are because I don't have a package.json file in the folder.  Add adal-node and also request.  Check they are here.

2.2 NodeJS code

Go back to the function and add this code

var request = require("request");
var adal = require("adal-node");

module.exports = function(context, req) {
    context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);

    var authorityHostUrl = 'https://login.microsoftonline.com';
    var tenant = 'johnliu365.onmicrosoft.com';
    var authorityUrl = authorityHostUrl + '/' + tenant;
    var clientId = '37ded58a-YOUR-CLIENT-ID';
    var clientSecret = 'fE4kulPjYOUR-CLIENT-SECRET=';
    var resource = 'https://graph.microsoft.com';

    var authContext = new adal.AuthenticationContext(authorityUrl);

    authContext.acquireTokenWithClientCredentials(resource, clientId, clientSecret, function(err, tokenResponse) {
        if (err) {
            context.log('well that didn\'t work: ' + err.stack);
            context.done();
            return;
        }
        context.log(tokenResponse);

        var accesstoken = tokenResponse.accessToken;
        var options = { 
            method: 'GET', 
            uri: "https://graph.microsoft.com/beta/groups", 
            headers: { 
                'Accept': 'application/json;odata.metadata=full',
                'Authorization': 'Bearer ' + accesstoken
            }
        };

        context.log(options);
        request(options, function(error, res, body){
            context.log(error);
            context.log(body);
            context.res = { body: body || '' };
            context.done();
        });
    });
};

Run it

Got our auth token, and got our list of groups.  Perfect.

2.3 Call SharePoint Online

var request = require("request");
var adal = require("adal-node")

module.exports = function(context, req) {
    context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);

    var authorityHostUrl = 'https://login.microsoftonline.com';
    var tenant = 'johnliu365.onmicrosoft.com';
    var authorityUrl = authorityHostUrl + '/' + tenant;
    var clientId = '37ded58a-YOUR-CLIENT-ID';
    var clientSecret = 'fE4kulYOUR-CLIENT-SECRET=';
    //var resource = 'https://graph.microsoft.com';
    var resource = 'https://johnliu365.sharepoint.com';

    var authContext = new adal.AuthenticationContext(authorityUrl);

    authContext.acquireTokenWithClientCredentials(resource, clientId, clientSecret, function(err, tokenResponse) {
        if (err) {
            context.log('well that didn\'t work: ' + err.stack);
            context.done();
            return;
        }
        context.log(tokenResponse);

        var accesstoken = tokenResponse.accessToken;
        /*
        var options = { 
            method: 'GET', 
            uri: "https://graph.microsoft.com/beta/groups", 
            headers: { 
                'Accept': 'application/json;odata.metadata=full',
                'Authorization': 'Bearer ' + accesstoken
            }
        };
        */
        var options = {
            method: "POST",
            uri: "https://johnliu365.sharepoint.com/_api/web/lists/getbytitle('Poked')/items",
            body: JSON.stringify({ '__metadata': { 'type': 'SP.Data.PokedListItem' }, 'Title': 'Test ' + (req.body.name || "hello!") }),
            headers: {
                'Authorization': 'Bearer ' + accesstoken, 
                'Accept': 'application/json; odata=verbose',
                'Content-Type': 'application/json; odata=verbose'
            }
        };


        context.log(options);
        request(options, function(error, res, body){
            context.log(error);
            context.log(body);
            context.res = { body: body || '' };
            context.done();
        });
    });
};

Changed resource to sharepoint.com, also changed the POST options - I want to insert a list item.

This is so sad!  "Unsupported app only token."  :-( :-(

1.1 Backtrack

To move on, we need to backtrack a bit.  @richdizz has a great blog post that documents this - to perform App Only operations on SharePoint Online, the client ID / Client Secret doesn't cut it.  You need to get auth token via certificate.

https://blogs.msdn.microsoft.com/richard_dizeregas_blog/2015/05/03/performing-app-only-operations-on-sharepoint-online-through-azure-ad/

Rich also pointed me to Travis Tidwell (Form.IO) 's excellent

https://github.com/formio/keycred

npm install -g keycred
keycred
(go through the options)

You need three things:

1. The generated keyCredentials JSON

You need to add this part to your AD App Manifest json file - under keyCredentials.  Your JSON should have a really long value for "value"

2. The private key

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAveX4jNn/eBPM1kdRNMPAlh6rT/JFoah9QkUbeYPkYGqWvn7X
~~snipped~~
0ibMc7T5K7AGVT0q0ppBLheFQkeSnPbHJrX40xILEkzd/0RLvC8X
-----END RSA PRIVATE KEY-----

Copy the entire thing, including -----BEGIN and END ----- and save this into a file.  I called mine funky.pem
Upload the funky.pem file into the folder.  I use the VS Online tool.
 

 

3. Certificate Fingerprint:

85b82741408c6c3af462b3a378e3e8963efaad70

2.4 Update to acquireTokenWithClientCertificate

var request = require("request");
var adal = require("adal-node");
var fs = require("fs");

module.exports = function(context, req) {
    context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);

    var authorityHostUrl = 'https://login.microsoftonline.com';
    var tenant = 'johnliu365.onmicrosoft.com';
    var authorityUrl = authorityHostUrl + '/' + tenant;
    var clientId = '37ded58a-YOUR-CLIENT-ID';
    var clientSecret = 'DONT NEED USE CERT';
    //var resource = 'https://graph.microsoft.com';
    var resource = 'https://johnliu365.sharepoint.com';

    var thumbprint = '85b82741408c6c3af462b3a378e3e8963efaad70';
    var certificate = fs.readFileSync(__dirname + '/funky.pem', { encoding : 'utf8'});

    var authContext = new adal.AuthenticationContext(authorityUrl);

    authContext.acquireTokenWithClientCertificate(resource, clientId, certificate, thumbprint, function(err, tokenResponse) {
    //authContext.acquireTokenWithClientCredentials(resource, clientId, clientSecret, function(err, tokenResponse) {
        if (err) {
            context.log('well that didn\'t work: ' + err.stack);
            context.done();
            return;
        }
        context.log(tokenResponse);

        var accesstoken = tokenResponse.accessToken;
        /*
        var options = { 
            method: 'GET', 
            uri: "https://graph.microsoft.com/beta/groups", 
            headers: { 
                'Accept': 'application/json;odata.metadata=full',
                'Authorization': 'Bearer ' + accesstoken
            }
        };
        */
        var options = {
            method: "POST",
            uri: "https://johnliu365.sharepoint.com/_api/web/lists/getbytitle('Poked')/items",
            body: JSON.stringify({ '__metadata': { 'type': 'SP.Data.PokedListItem' }, 'Title': 'Test ' + (req.body.name || "hello!") }),
            headers: {
                'Authorization': 'Bearer ' + accesstoken, 
                'Accept': 'application/json; odata=verbose',
                'Content-Type': 'application/json; odata=verbose'
            }
        };


        context.log(options);
        request(options, function(error, res, body){
            context.log(error);
            context.log(body);
            context.res = { body: body || '' };
            context.done();
        });
    });
};

Use fs.readFileSync to read funky.pem file and acquireTokenWithClientCertificate instead of ClientSecret

Result

Note - updated by "funky"

Triggers

There are several Azure Functions triggers that immediately jumps out at me as being really useful.

HTTP Trigger

Your JavaScript can call the azure function and trigger the action.  You will need to turn on CORS.  And filter to only your domain.

But this will give your client side javascript ability to call an elevated function.

Timer Jobs

You can trigger your function based on a timer.  In this scenario, your javascript will run against SharePoint like a timer job. 

WebHooks

Currently only Exchange in Office 365 supports WebHooks.  But SharePoint will support it in the future.  When this becomes available, a webhook can trigger an Azure Function and essentially, you have a List Item event receiver.

Except all in JavaScript.

So much fun haha I'm so excited!

Further Reading

 

Hype Level Insane - #FutureOfSharePoint

I wrote this as an email to colleagues.  But it got so long and exciting with pictures I decided to publish it.  There are no spoilers and I don't know any secrets.

 

 

#FutureOfSharePoint event is a keynote follow by sessions

There are more sessions after the keynote by Jeff Teper on whatever they will announce, so if you are a SharePoint guy you might be up for a looooonnnnng day.

Venue setup.  Small invite only place.

 

A lot of SharePoint MVPs have invitations to be physically at the event – there’s a significant gathering of them.  Perhaps Microsoft will consider giving our SharePoint MVP titles back 😉  Office Server and Services is nice.  But being a SharePoint MVP was special.

A group of SharePoint MVPs.  A murder of SharePoint MVPs?

There are times I really think about moving to America.  This is one of those times.

 

Australian Timezone

I’m debating whether to sleep really early and wake up at 2am.  Or just read it tomorrow – I’m pretty sure there will be Sway, liveblog, twitter coverage #FutureofSharePoint, yamjams and a number of MVP impression videos after the event.

I do hope the event will be broadcasted via Skype-broadcast.  Eat your own dogfood Team Office.

Next week Collab365 is broadcasting from MS HQ.  I hope they are setting up with MS and will utilize Skype to run their online conference.  In the past years we have used YouTube.

There is a PowerBI livequery dashboard set up with MS’s new Microsoft Flow

http://whitepages.unlimitedviz.com/2016/05/integrating-microsoft-flow-power-bi-real-time-reporting/

 

SharePoint Can't Be "Dead"

Reading the tea-leaf from what’s public:  To people that says “SharePoint is dead”...

Jeff Teper would not have returned to this role and be “Corporate Vice President of OneDrive and SharePoint” only to tell the world SharePoint is dead.

MS would not invite 20+ SharePoint MVPs to a physical event to tell the world SharePoint is dead.

My expectation is that the news will be really positive.  But my personal hype level is already far too high.

 

Key MS people to follow:

https://twitter.com/jeffteper  Father of SharePoint, Corporate VP.

https://twitter.com/mkashman

https://twitter.com/williambaer

https://twitter.com/AdamHarmetz 

https://twitter.com/danholme   Founder of IT Unity and MVP, recently joining Microsoft to continue Office evangelism. 

 

This rant is already too long and with pictures I might make this a blog post.  But no guesses from me.  Things will either get released with much fanfare, or they won’t.  😊

AU Digital Workplace Conference - loving SharePoint and community

I had a wonderful time in Melbourne at the Digital Workplace Conference, hosted by Debbie Ireland and Mark Rhodes.  A big thank you and congratulations for your twentieth event!

And several international SharePoint superstars: Joel, Michael, Marc Anderson and Laura Rogers and lots of local SharePoint and Office 365 experts.  Aside from the local Melbourne crowd, there were many from Sydney, Perth and Brisbane.  It was great to chat with you all.

On my presentation

I had one apology, I really didn't plan the "title" of my session well.  The talk I pitched was a "Office Add-Ins may seem irrelevant to the SharePoint Developer, but actually, it isn't.  Here's how you can upskill."

This was a talk about upskilling, building on little steps and having solid foundations for each step before bravely taking the next step.

Instead, the title of the talk "Bravely take your skills to the next level: Office Add-ins" gave a slightly wrong message.  I shall rename the topic:

"Bravely take steps to skill up to your next level - from SharePoint to Office Add-ins"

Here's the presentation

Download Bravely

SharePoint Events and news catch up for Australia - April/May 2016

I was preparing this list for our April Sydney SharePoint Usergroup event.

April 28-29 Digital Workplace Conference in Melbourne

tile-blog-dwc.png

This is the annual Australian SharePoint Conference / ShareThePoint Conference by Debbie Ireland.  The biggest Australian SharePoint show for the year.  A lot of opportunity for networking, user cases and physically meeting and talking with the community experts.  Lots of Australian and overseas visiting SharePoint experts across the spectrum (IT Pro, Business, Power User, Dev) at this event.  http://dwcau.com.au/  

 

April 28-29 Cloud Roadshow in Sydney

This is a Microsoft event for a developer audience.  Focused on Azure.

https://microsoftcloudroadshow.com/sydney

 

May the 4th - Future of SharePoint

This is an streaming online event with Jeff Teper, the father of SharePoint (and now OneDrive).  It would be live on May the 5th for Australians and probably very early in the morning.  Check timezones.

https://resources.office.com/en-us-landing-the-future-of-sharepoint.html

 

May 7 Office 365 Saturday Perth

The SharePoint / Office 365 Saturday events for 2016 kicks off again from Perth.

http://www.o365saturdayaustralia.com/perth

 

May 10-12 Collab365 Online Summit

The SP24 / Collab 365 guys are back again for 2016 with a huge lineup of streaming and recorded sessions.

http://collab365.events/collab365-summit-2016/

 

OneDrive for Business

There was a recent blog post was really good with information on 2016 roadmap.

https://blogs.office.com/2016/04/12/onedrive-for-business-recognized-as-an-efss-leader-and-continues-momentum-with-spring-updates/#wsEJ4t8xOHg24iYE.97

(Ignore the marketing intro about industry recognition… the roadmap items like Win 8.1, pause sync, and also Doc Lib sync in 3rd quarter 2016 is good info)

 

New Document Library UX went live

https://support.office.com/en-us/article/What-is-a-document-library-3b5976dd-65cf-4c9e-bf5a-713c10ca2872?ui=en-US&rs=en-US&ad=US

Almost universally, the reception is cautious.  We like seeing updates.  We don't like missing ribbons and hiding team collaboration features.  Robert Crane has one of the earliest pieces on this http://blog.ciaops.com/2016/04/this-seems-like-siloing-to-me.html that I really echo with.

UserCustomAction-ConfigPage adding CSS file links

I've had several questions in comments about how to add CSS files using the Simplest-Safest-Most-Future-Proof-Way to customize your SharePoint and SharePoint Online using UserCustomActionConfigPage

This is something I wanted to talk about from day 1 of this series - as I did cover you can add more than 1 CSS files, and specify their ranking via the Sequence number.

Because UserCustomAction doesn't create a link for CSS references, the way to do this is via the ScriptBlock and create a LINK element and attach it on page creation.

How to use this?

Grab the latest configure-page.aspx from Github.

Reference a CSS file instead of a JS file.  The ID is optional, but will be respected if specified.

The Sequence is honoured - 1001 will follow 1000.

Result - the CSS reference is loaded into the page.

Happy branding!