I was reading through one of Bob German’s (@Bob1German) recent (and great!) posts about Porting REST calls to SharePoint Framework and thinking about a conversation Bob, Julie (@jfj1997) and I had at our “salon”* when I was reminded of a very specific truth about our working days.
I would venture to say that some ridiculously high percentage of all of our time developing with SharePoint and Office 365 comes down to figuring out why we can’t get something to work due to authorization or authentication issues. Some say this is a necessary evil to create enterprise software, but I say it’s an unnecessary problem. Unfortunately, documentation about auth stuff tends to be tremendously opaque and difficult to understand. In the same way that I expect the server to be there and I want nothing to do with it directly, I want auth stuff to just work – easily.
Sadly, this has never been the case with so-called “enterprise-class” software. On some level, I think the obtuseness of auth is there on purpose to keep the bar high enough to exclude lower end hackers. Unfortunately, we all get caught up in the kudzu of auth as a corollary effect.
Ok, so that’s my editorial on auth. I wish it were easier, but it isn’t.
Recently in one of my Single Page Applications (SPAs) for a client, we kept getting weird failures in posting data to a list. Weird mainly in that I never saw the error on my end, my client is far away, and screen sharing is tricky due to the technical proficiency on that end. I’m not dissing anyone; they are great at what they do, but debugging JavaScript with me is not in their wheelhouse.
If you write software, you know that the worst bugs to squash are those that happen sporadically, and only on someone else’s machine – especially if you don’t have direct access to that machine. As usually, though, it was simply me doing something dumb with auth – which is not easy. Have I mentioned that?
Basically, the problem was that while I was fetching the request digest from the page (this is a “classic” page in SharePoint Online), I wasn’t ever refreshing the token. In this application, people move around from page to page enough and use the application for short enough time periods that we simply hadn’t seen the problem in testing.
Smart people will think “Marc’s an idiot” on this one, but I play the fool so you don’t have to.
The code comes from a service I use everywhere in my applications for this particular client. It’s basically a set of utility functions that are useful when you’re using Angular with SharePoint. I’ve built the SPA using AngularJS (1.x), so my code below represents that. However, similar logic can work with jQuery or whatever instead of AngularJS and $q. I adapted it from some Typescript code Julie was using, so credit there, with a bit of add on from me. I’ve added a bunch of comments and have also left some of the console logging I used in debugging – but commented out.
// Initialize with the token in the page in case there's a call that happens before we get a fresh token self.requestDigest = document.getElementById("__REQUESTDIGEST").value; // This function makes a call to the contextinfo endpoint to fetch a new token self.getRequestDigest = function (subsite) { var deferred = $q.defer(); // This request must be a POST, but we don't need to send any data in the payload // i.e., it's a very simple call var request = { url: subsite + "/_api/contextinfo", method: "POST", data: '', headers: { 'Content-Type': 'application/json', "Accept": "application/json;odata=nometadata" } }; var success = function (response) { deferred.resolve(response.data) } var error = function (response) { deferred.reject(response.data); }; $http(request).then(success, error); return deferred.promise; } // We call this function once when the page loads to set up the refresh looping self.refreshRequestDigest = function (subsite) { // Set the default delay to 24 minutes in milliseconds var delayMilliseconds = 1440000; // Call the function above to get a new token self.getRequestDigest(subsite).then(function (contextInfo) { // console.log(new Date() + " Old token:" + self.requestDigest); // Save the new token value in a variable available to all controllers using this service self.requestDigest = contextInfo.FormDigestValue; // Calculate the number of milliseconds which will be two minutes before the old token will expire delayMilliseconds = (contextInfo.FormDigestTimeoutSeconds * 1000) - 12000; // console.log(new Date() + " New token:" + self.requestDigest); // console.log("Waiting for " + delayMilliseconds + " ms"); // Use setTimeout to set up the next token request setTimeout(function () { self.refreshRequestDigest(subsite); }, delayMilliseconds); }); }; // And here's the initial call to get things rolling. We need a token for the current site self.refreshRequestDigest(_spPageContextInfo.webAbsoluteUrl);
The data we get back looks something like this:
{ "FormDigestTimeoutSeconds": 1800, "FormDigestValue": "0xD22256967BAA39D5860CF0E812BE4AD0E018603851720D0E57581E1D1EBE8394865FF07EA3905DA1FE21D19D25BD6369B9D4F0F47725FBBB5AA7B3DE4EB54BAA,28 Jun 2017 15:21:52 -0000", "LibraryVersion": "16.0.6621.1204", "SiteFullUrl": "http://ift.tt/1H2SDM5", "SupportedSchemaVersions": [ "14.0.0.0", "15.0.0.0" ], "WebFullUrl": "http://ift.tt/2v9ZG6t" }
The thing we care about most is the
FormDigestValue, as that’s what we use to make our POSTs to SharePoint while it’s valid. But also note that there is an attribute in the returned JSON for
FormDigestTimeoutSeconds. That’s the number of seconds that this particular token will be valid. In every tenant where I’m using the code, that works out to be 30 minutes. However, there may well be a setting which can change that, or Microsoft may change the time span on us. Because of this, I use the value to calculate how often to request a new token: T-2 minutes.
I’m pretty sure that there are pages in SharePoint which don’t do this correctly, so I’m not feeling like too much of an idiot. For example, when we click on an “app” – usually a list or library to you and me – in Site Contents, it opens in a new browser tab. Very often when I go back to that Site Contents page – when it has been sitting there for a while – I’ll get an unauthorized error. This may be fixed by now, though it has happened to me a lot.
I hope this is helpful. Using functions like this, we can make the whole auth thing easier on ourselves – and there zero reason to need to write this code from scratch every time. Store int in one place and if anything changes, you’ll have one place to fix it later.
- We have occasional “software salons” – usually in one of our homes – where we simply get together to kick around what’s going on in our work lives, interesting things we’ve solved, etc. It’s a tremendously educational and useful exercise. I would encourage you to do something similarly informal if you don’t have enough water cooler time. At Sympraxis, we have no water cooler.
by Marc D Anderson via Marc D Anderson's Blog
No comments:
Post a Comment