Interact with Graph and make O365 Groups with AzureFunctions PowerShell

In this post, we talk about how to get an access_token in PowerShell to talk to the Microsoft Graph, so we can run automated non-interactive scripts in Azure Functions.

 

I'm so sorry for butchering the title every time.  But this blog post is cool.

I've written previously about creating SharePoint sites via PnP-PowerShell.  Which is very powerful and very popular - thank you for all the nice discussions and comments.  Soon, people asked - can we do the same for O365 Groups?

The PnP-PowerShell cmdlet for Connect-PnPMicrosoftGraph works, but it raises an login dialog, which makes it a show stopper for non-interactive scripts or Azure Functions.  I took it upon myself to find a workaround over my December holidays but alas the solution didn't come until January. 

What is the Trick?

The problem is to authenticate with Azure AD and get an access token that we can use to talk to the Office 365 Graph.  The trick, is a little not-well known thing called Resource Owner grant.

grant_type=password

I have a few links about the Resource Owner grant type at the end.  Basically, this grant_type lets you use username/password to obtain an access token.

Setting up Azure App Registration

You are probably familiar with these steps by now.  Today's screenshots came from the new Azure AD Portal.

Navigate to the Azure Active Directory portal, before we go further, grab the Directory ID - this is the tenant ID you'll need.  The tenant ID isn't a private value - it appears in URLs often when you are signing into Office 365.  But it's handy, right here, so take a copy.

Create our app.

Add Delegate Permission to Read and Write all groups.  Note this requires admin permissions.

Click the awesome Grant Permissions button at the top of the permissions registration, this grants it for users in your Active Directory.

You will need some clientsecrets - create them from Keys.  I like them not expiring for a long time.  So I pick the Never expires option.  It'll expire when I'm long gone.

Copy down your ClientSecret

One final check.  Copy the Application ID.  This is your app's Client ID.

To Azure Functions!

 

First, we create another HttpTrigger-PowerShell.

Because we aren't using PnP-PowerShell this time, there is no extra modules dependency.  This example is going to be simply Invoke-WebRequest and Invoke-RestMethod

Start with this code:

$requestBody = Get-Content $req -Raw | ConvertFrom-Json

$username = "[email protected]";
$password = $env:PW;
$client_id = "9308c103-8208-40cd-85e2-37a994b3578d";
$client_secret = $env:CS;
$tenant_id = "26e65220-5561-46ef-9783-ce5f20489241";
$resource = "https://graph.microsoft.com";

# grant_type = password

$authority = "https://login.microsoftonline.com/$tenant_id";
$tokenEndpointUri = "$authority/oauth2/token";
$content = "grant_type=password&username=$username&password=$password&client_id=$client_id&client_secret=$client_secret&resource=$resource";

$response = Invoke-WebRequest -Uri $tokenEndpointUri -Body $content -Method Post -UseBasicParsing
$responseBody = $response.Content | ConvertFrom-JSON
$responseBody
$responseBody.access_token

Out-File -Encoding Ascii -FilePath $res -inputObject $responseBody

Change to your own username/password/clientid/clientsecret/tenantid - note my password and clientsecret are stored in Function App Settings.

When this Azure Function runs -

You'll see the $responseBody and specifically, the $responseBody.access_token

We now have a key.

Calling Graph - Get Groups

$access_token = $responseBody.access_token

# GET https://graph.microsoft.io/en-us/docs/api-reference/v1.0/api/group_list
$body = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/groups" -Headers @{"Authorization" = "Bearer $access_token"}
$body | ConvertTo-JSON

Out-File -Encoding Ascii -FilePath $res -inputObject $body

Call graph, attach access_token in Header.

My Office 365 groups in JSON.  Notice the function took about 1 second between start, obtain access_token, and retrieve graph.

Calling Graph - Post Group

The Graph is not a read-only datasource, and our App has Group.ReadWrite.All

# POST - this creates groups https://graph.microsoft.io/en-us/docs/api-reference/v1.0/api/group_post_groups
$body = @{"displayName"="ps-blog"; "mailEnabled"=$false; "groupTypes"=@("Unified"); "securityEnabled"=$false; "mailNickname"="ps1" } | ConvertTo-Json 
$body = Invoke-RestMethod `
    -Uri "https://graph.microsoft.com/v1.0/groups" `
    -Headers @{"Authorization" = "Bearer $access_token"} `
    -Body $body `
    -ContentType "application/json" `
    -Method POST
$body | ConvertTo-JSON

Out-File -Encoding Ascii -FilePath $res -inputObject $body

Here we go.  A new O365 group "ps-blog" is created.  Note the function took 3 seconds.

Why I like PowerShell to demo Functions

  1. Code looks simple.  Cmdlets compresses a lot of functionality. 
    Take for example ConvertTo-Json or ConvertFrom-Json.  In C# we'd have to call a JsonSerializer and in NodeJS we'd need double promises just to get a json from fetch()
  2. PowerShell wraps C# and .NET libraries so they can be fairly powerful without a lot of code
  3. Not everyone understand JavaScript, or NodeJS JavaScript
  4. Suitable for IT Pros that may only know PowerShell
  5. Developers should learn more languages - and you can convert this in your head to C# or NodeJS anyway.

Bonus

This part is an exercise for the reader.  Make an PowerApps app that will call this function - passing in a parameter for the group name.  Hook up the Azure Function as a connection and bind it to a textbox and a button press.

Ignite Australia

Come and see me present all these cool techniques and more at Ignite Australia!

https://msftignite.com.au/sessions/session-details/1988/serverless-in-office-365-build-services-with-azure-functions-prod324

We'll talk about the Azure Functions story for today's Office 365 Developer, IT Pro and Power User.

Azure Functions has an amazing place from microservices to Swagger to the BotFramework!

 

Summary

  • Use PowerShell
  • Get access_token via username/password
  • Get our graph
  • Make O365 Groups

References

 

If you liked this post - please leave a comment below.  Please also try the code and let me know how it goes for you.  I love reading about people taking my brief demo code further to solve real problems.

Running OfficeDev PnP cmdlets in 32bit AzureFunctions

Some recent changes with AzureFunction created a small problem - x64 functions are not available in the starter-consumption plans.

The situation is not game over.

Since all the assemblies are not compiled for x64 only.  So just open the psd1 file and change 'Amd64' to 'None'.  They actually run just fine.

 

Build your PnP Site Provisioning with PowerShell in Azure Functions and run it from Flow

What if I tell you - you can build your own Azure Function and use PnP to provision SharePoint Online sites without firing up Visual Studio?

What if I tell you - we will follow the best practices from PnP.  And You can do this all within 10 minutes (OK I timed it - if you don't take any breaks it'll be 10 minutes), and we'll do it with PnP PowerShell.

You will think - this is magic.  You will probably say, this is all crazy.

What if I tell you - you can connect this to the newly released Microsoft Flow.  So you don't even need to learn WebHooks (which is awesome and you should learn it).

What if I tell you - the whole thing will cost you a little bit.  It's not totally free.  It will cost you may be two cents.  ($0.02)

You will say, get out.  I need to lie down.

Learning Software pieces like LEGO blocks

I see software pieces as I see LEGO blocks.  PnP Provisioning is a block (a .NET library written in C#).  PowerShell cmdlets are a block.  And Azure Functions is a block.  Flow is a block.

What would happen if you connect them together?

 

Setting up PowerShell from PnP

First, this is how we set up in our local machine:

Install-Module SharePointPnPPowerShellOnline

Why PowerShell? 

Why PowerShell, you ask - John you are a developer.  You are a .NET developer before you became a JavaScript developer.  Why PowerShell suddenly?

The answer is simple.  It is because everybody understand this:

# connect to the SPWeb context
Connect-SPOnline -url "https://sharepointgurus365.sharepoint.com/sites/projects/template"
# export (copy) a template
Get-SPOProvisioningTemplate -force -out project-template.xml

# connect to new blank SPWeb
Connect-SPOnline -url "https://sharepointgurus365.sharepoint.com/sites/projects/tea"
# paste the template
Apply-SPOProvisioningTemplate -path project-template.xml

Four lines - you have just copied a template from /projects/template and stamp it over to /projects/tea - this is the power and the hardwork of the PnP provisioning team.  They make this magic look simple.

PnP was updated about a week after the blogpost and several PnP commands have been renamed to avoid confusion with SharePoint admin commands. See PnP-PowerShell documentations.

Connect-SPOnline is now Connect-PnPOnline
Get-SPOProvisioningTemplate is Get-PnPProvisioningTemplate 
etc

A way to verify the powershell is to run it first on your local machine and make sure it works first, before uploading the same DLL modules and script to Azure Function.

Special thanks to @pskelly

 

But you can do this in C#

Of course, you can also do this in C#, it looks like this:

This shouldn't be too scary to a developer.  But everyone sees the bubbles and the flow chart and just stop trying.  It "looks" hard.

Additionally, in CSOM - everything is asynchronous.  C# makes this simpler with the await syntax, but in the PowerShell cmdlets, every request is essentially serialized - so they are easy to understand, even if they aren't the fastest way. 

If you want to see what this looks like in NodeJS - I have several blogs.  It's great to learn.  If you don't like JavaScript to begin with - then you'll probably run to PowerShell with open arms.

OK enough chatter - let's go back to the demo.

 

Setting up Azure Functions

Create a new HTTP Request function - select PowerShell as the language.

It says Experimental - what they really should say is "The World's Not Ready For This".

PowerShell Azure Functions looks like this.  The request is passed in as raw JSON content - which is parsed into an object.

Output from the function by writing out to the $res file.

 

Install-Module

install-module doesn't work.  But we still can have modules.

http://stackoverflow.com/questions/37724769/how-to-install-a-powershell-module-in-an-azure-function/39985646#39985646  - The steps are explained by Azure Function team's @tohling

Essentially, we can take a PowerShell Module, find all its component script and DLL, and copy them to a modules/ subfolder within the Azure Function.  It will be loaded when the function needs to run.

In Functions - go to Kudu

Navigate to your new function's directory, and add a modules subfolder

In your PC:

Navigate to

C:\Program Files\WindowsPowerShell\Modules\SharePointPnPPowerShellOnline\2.8.1610.1\

(if you don't have this folder - you missed Install-Module SharePointPnPPowerShellOnline from earlier)

You will also need Microsoft.IdentityModel

C:\Windows\assembly\GAC_MSIL\Microsoft.IdentityModel
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.IdentityModel.Extensions

Grab all these DLLs and PowerShell script and throw them all inside modules.

 

Change Azure Function to x64

One more thing.  The PowerShell cmdlets prefer x64 architecture.  Azure Functions provide 32 bit by default.  Switch this in function settings > application settings > Platform

 

Setup Done!

Now we can write fun functions!  Go back to our site provisioning function.

$requestBody = Get-Content $req -Raw | ConvertFrom-Json

# {
#     "source": "https://sharepointgurus365.sharepoint.com/sites/demo02/projects/pst",
#     "destination": "https://sharepointgurus365.sharepoint.com/sites/demo02/projects/tea"
# }

$source = $requestBody.source
$destination = $requestBody.destination

$secpasswd = ConvertTo-SecureString $env:SPO_P -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential ($env:SPO_U, $secpasswd)

Connect-SPOnline -url $source -Credentials $mycreds 
Get-SPOProvisioningTemplate -force -out "D:\home\site\wwwroot\project-template.xml"

Connect-SPOnline -url $destination -Credentials $mycreds 
Apply-SPOProvisioningTemplate -path "D:\home\site\wwwroot\project-template.xml"

Out-File -Encoding Ascii -FilePath $res -inputObject "Done $destination"

Note: PnP was updated about a week after the blogpost and several PnP commands have been renamed to avoid confusion with SharePoint admin commands. See PnP-PowerShell documentations.

Connect-SPOnline is now Connect-PnPOnline
Get-SPOProvisioningTemplate is Get-PnPProvisioningTemplate 
etc

A way to verify the powershell is to run it first on your local machine and make sure it works first, before uploading the same DLL modules and script to Azure Function.

Special thanks to @pskelly

The script will run unattended.  So you will need to provide your username/password stored in Function Settings and read from $env variables.

Run success!

What do we have here?!  PowerShell cmdlets that wrapped the PnP C# library and talks to SharePoint Online and everything running inside an Azure Function.

You have a service.

If you are counting lines - there is 10 lines of PowerShell - including the function wrapper.

 

Flow

Let's top off this demo with a Microsoft Flow - this was GA'ed just this week.

Create a list "Copy-Site" - with two fields "ToSite" and "FromSite"

Have the flow trigger off ItemAdded, and call our Azure Function via the Http Request.

Pass in source and destination.

 

Test Flow... workflow

Flow tells you what was sent to the HTTP Request, and what Response body it got back.  Use this for debugging your functions.

 

We always talk Costs because Azure Function is cheap

The 11cents total is over several days, and the breakdown shows it's almost entirely bandwidth costs - which doesn't have a free bucket.  My AzureFunctions don't exceed the allowed free tier, so I'm not paying for any compute time.

 

Where do we go from here?

What can you do with PowerShell?  Desired State Configuration?  Download lists to CSV?  Synchronize and fix metadata?  Locate orphaned or misplaced documents and fix that?  Improve and merge user profile properties?  Provision users and sites and what else do you fancy?

Does this give you wild ideas?

 

Azure Functions is Serverless

When we say Azure Functions is Serverless - it always seems we are saying we don't need servers (and thus don't need IT Pros).  This isn't strictly the truth - which is that the server is maintained by professionals, and developers could focus on writing code.

But after working on these PowerShell for the last two weeks - actually, I'm beginning to wonder whether there will be a day we don't need Developers.

An analyst says to a Bot - I want this System A to connect to this System B and put result in this System C.  The bot arranges three connections and suggest how the parameters will be connected.  Offers to perform a trial run.  Analyst checks it and approves it.  The code goes into production.

Think about this.  Everyone needs to evolve.  IT Pros - you have a huge opportunity here to use your PowerShell skills to provide huge business value.  Developers - you should use the best tools to enable your users and do more, this is really easy to get into.

 

Tell me what you think

I really want to know what you think - if you can spend a bit of time and leave a comment below.  Where are we going as developers, where are we going as IT Pros?  What kind of PowerShell-AzureFunction are you going to build?  Does PnP need a repository for PowerShell recipes?

It's so nice to see the pieces fit together well.

Working with SharePoint WebHooks with JavaScript using an Azure Function

This blog post covers additional explanation on how to subscribe to SharePoint WebHooks and have it running with only JavaScript in Azure Functions.  The entire code is in one single JavaScript file.

The SharePoint team delivered on the promise to ship SharePoint WebHooks, and made an reference example in C#.  Be sure to watch the PnP webcast as well.

What is Azure Functions?

Azure Functions, in the simplest sense, is a Azure WebJob that lets you run a JavaScript function in a file (it can do C# too), and it will run it for you when you trigger it.  Because it is a Serverless platform - you don't pay for the WebJob unless your function is running.  This makes it really economical (you also get like a million free runs per month...)

Azure Function comes with a super user-friendly UI and you can paste or upload your JavaScript directly in the browser.  You don't need any tools installed.

Running in the browser

 

Of course, this is server-JavaScript.  So think NodeJS.  We can talk to lots of REST APIs (Graph, SPO) but there will be no browser DOM.

What can you do with this?

You can trigger it on a timer.   Hey that sounds like a SharePoint Timer Job.

You can trigger it on a web request.  This is super useful if you have a client-side UX and you need a button to do some high level elevated permission action.  You call the Azure Function to do it for you, using an App-Only permission elevation.

Why is SharePoint WebHooks important?

A SharePoint WebHook is a REST endpoint that you can attach a remote End Point to.  Right now, there is only a List endpoint.  So any updates to the list items will trigger an event, and SharePoint will call your function.

Hey.  Wait that sounds like a SharePoint Remote Event Receiver.  You are right!

Design

So the idea of our function is this - when triggered:

It will Auth and then talk to SharePoint REST and do one of the following.

  • Check if it has a request parameter "subs" - then it will list the current subscriptions on our target list
  • Check if it has a request parameter "sub" - it will try to attach itself to the target list
    SharePoint will immediately call the function with a validationtoken parameter so…
  • Check if the request has a validationtoken parameter - it will immediately reply with that token as text/plain.
  • Skipping all three conditions, it will run the default action
    The default action is that it will add an list item in a different destination list.  Because the function is running on its own App-Only permission, it can update a list that the original user doesn't have access to.

 

GET Subscriptions

options = {
    method: 'GET',
    uri: "https://johnliu365.sharepoint.com/_api/web/lists/getbytitle('subscribe-this')/subscriptions",
    headers: headers
};
request(options, function (error, res, body) {
    context.log(error);
    context.log(body);
    context.res = { body: body || '' };
    context.done();
});

GET subscriptions.  Array result is [] empty by default.  It has subscriptions after you attach hooks successfully.

 

POST Subscription (to add itself)

options = {
    method: 'POST',
    uri: "https://johnliu365.sharepoint.com/_api/web/lists/getbytitle('subscribe-this')/subscriptions",
    body: JSON.stringify({
        "resource": "https://johnliu365.sharepoint.com/_api/web/lists/getbytitle('subscribe-this')",
        "notificationUrl": "https://johnno-funks.azurewebsites.net/api/poke-spo2?code=q8sq9wxm62asd-YOURTRIGGERCODE",
        "expirationDateTime": "2017-01-01T16:17:57+00:00",
        "clientState": "jonnofunks"
    }),
    headers: headers
};
request(options, function (error, res, body) {
    context.log(error);
    context.log(body);
    context.res = { body: body || '' };
    context.done();
});

Sending POST subscription without handling validation token.  The request fails.

 

Handle Validation Token

// if validationtoken is specified in query
// immediately return token as text/plain
context.log(req.query);
context.log(req.query.validationtoken);
context.res = { "content-type": "text/plain", body: req.query.validationtoken };
context.done();

The function is called twice.  Second time by SharePoint to validate the subscription.

Result Video

The Poked list item was created by "SharePoint App" not "John Liu" the user.

Notes

This demo builds on top of the code from Azure Functions, JS and App-Only Updates to SharePoint Online that covers authentication with certificate, and running AppOnly permissions. 

Additionally, I've moved ClientID and Certifate ThumbPrint to Azure Function App Settings.  This means they are no longer part of the code.

App Settings

var clientId = process.env['MyClientId'];
var thumbprint = process.env['MyThumbPrint'];
Function App Settings > Configure App Settings

Function App Settings > Configure App Settings

 

Source Code

I'm making an effort to put all my demo source code on GitHub going forward.  This is part of the "Upgrade Your JS" demos.

https://github.com/johnnliu/demo-upgrade-your-js/tree/master/azure-function-web-hook

Let me know what you think about SharePoint WebHooks, Azure Functions and JavaScript that will rule everything ;-)

If you spot a bug or want to update the code - send me a Pull Request.

 

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-node
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