Engineered Code is proud to announce the availability of ecLearn - the Learning Management System built on top of Microsoft Dataverse

ENGINEERED CODE BLOG

Getting a SharePoint OAuth Access Token in a Dynamics 365 Web Resource

And now for something completely different! While I normally talk about Dynamics 365 Portals, we’ve had a few requests recently around SharePoint and Dynamics 365 integration, specifically around uploading documents to SharePoint via a custom interface. I initially thought that it would be a topic well covered on the Dynamics blogosphere but, to my surprise, I couldn’t find a lot out there. So I spent some time figuring it out, and thought I’d share my findings.

The Challenge

We wanted to create a web resource that allowed a user to upload a document directly to SharePoint. While we are aware that an out-of-the-box SharePoint integration exists, we wanted a bit more control, including a custom file name and additional metadata.

Uploading a document in chunks, setting the file name, and setting meta are all things easily accomplished via the SharePoint REST API. So we felt confident that what we were trying to do was possible. The trick was getting the authentication working without requiring the user to login specifically into SharePoint. Assuming that both Dynamics and SharePoint are in the same Azure AD tenant, we figured this should be possible.

To call the SharePoint REST API, all we needed was an OAuth token with the correct privileges. Since we’re already logged into Dynamics with the same Azure AD user, how hard could it be to get that access token?

Getting an Access Token Without the User Ever Knowing

First, I think it’s important to provide a little background on OAuth implicit flow and Azure AD.

In our solution, we know that in order to get access to the web resource, the user must login to Dynamics 365 first. If an organization is using Azure AD, this process with require their main Azure AD credentials, and once provided, typically users won’t need to re-enter them to access other resources secured with Azure AD. Typically authentication is handled via cookies or browser storage, and for security reasons you can only access cookies or storage that were created by the current domain. So how can you log in to a URL that ends with crm.dynamics.com and also be signed into another site that ends with sharepoint.com?

This is achieved by having users log in on a shared page. If using Azure AD, whether a user is logging into Dynamics or SharePoint, the domain of the URL of the login page is login.microsoftonline.com. Once you enter valid credentials, you are redirected to the appropriate application; that redirect includes a token that the application uses to log you into that system specifically. If you were to visit a different application, you would be redirected to the login.microsoftonline.com domain momentarily, but if you’re already authenticated you won’t be asked for you credentials and will be redirected immediately to that other application, again with a token, and you’ll be logged in.

It is possible with implicit flow for the token that is returned to be the access token we need to call the SharePoint REST API, and it is possible to do this without the user even noticing; it is a non-trivial, but still fairly straight-forward task. Thankfully there are libraries (like ADAL.js) that can do this for you. At a high level, the process looks something like:

  • A hidden iframe is placed on the page with a URL that requests the access token. This URL’s domain is login.microsoftonline.com, and contains parameters like the ID of user, the Azure AD tenant ID, the permissions that the user is requesting, and the URL to redirect to if the authentication was successful. This URL must be on the same domain as the current page.
  • A JavaScript event handler is attached to the iframe’s load event.
  • If the user is already authenticated (which in our situation, they should be), the iframe will get redirected to the URL we specified, with additional query string parameters, one being the access token. This triggers the load event handler.
  • Since the iframe now points to a page on the current domain, we are able to access its properties (if the iframe points at a page on a different domain, you’ll get a security error if you try to access any properties), including its URL and query string parameters. You now have the required access token.

Down the Rabbit Hole

While we are by no means SharePoint experts, we use it internally and on a number of client projects, and have recently spent a fairly large amount of time becoming more familiar with the Azure AD implementation of OAuth. Like many things, once we figured it out, it didn’t turn out to be overly complicated, but we ventured down a few wrongs paths before finding the correct method.

Again, the main challenge was: how do I acquire the access token that we need to pass in the Authorization header to the SharePoint REST API?

The SharePoint documentation seems to be more concerned with how to acquire these tokens from within SharePoint itself, since the documentation seems to target the development of SharePoint Add-ins. This didn’t seem too helpful for us.

Our first attempt was to use the same ADAL library that we’ve been using recently for Dynamics 365 Portals Companion Apps. By starting from the PBAL.js library (which I believe is derived from this), we tried to determine the necessary parameters to pass to the login.microsoftonline.com domain to acquire the token. The main parameter we had trouble with was scope, which is essentially the permission that we were requesting. We figured that this would have something to do with requesting access to SharePoint, but couldn’t find any value here that would give us a valid token that would work with SharePoint.

Next, we considered using the new Microsft Graph API for SharePoint, but it was in beta so we weren’t comfortable using it in production.

After a bit more trial and error, I stumbled upon this post, which provided the information I needed: in the past we’ve been using v2 of the Azure AD OAuth service; SharePoint right now only works with the v1 service.

Now, this is not to be confused with the version of OAuth. Both v1 and v2 of the Azure AD OAuth Implementation use OAuth v2.

Once we figured that out, and we discovered that one of the main differences between the two is that in v1 you don’t specify scope (which was the parameter we couldn’t figure out), but instead you pass a resource parameter, and this resource is simply the URL of your SharePoint instance (https://orgname.sharepoint.com), we were in business.

We updated the getAuthorizeUri function to be:

var b2cAuthUri = "https://login.microsoftonline.com/" + params.tenant + "/oauth2/authorize?" +
                "client_id=" + params.clientId +
                "&response_type=token" +
                "&redirect_uri=" + encodeURIComponent(redirectUrl) +
                "&resource=" + encodeURIComponent(params.resource) +
                "&response_mode=fragment" +
                "&state=12345" +
                "&nonce=12345" +
                "&prompt=none" +
                "&domain_hint=organizations" +
		"&login_hint=" + params.oid;
A few notes:
  • To change the version of the Azure AD OAuth service, simply just remove /v2.0 from the URL. Also, PBAL.js is meant for Azure AD B2C, so for just plain Azure AD, you don’t need /te or the policy portion of the URL.
  • We replaced scope with resource,.
  • The login_hint/parems.oid needs to be the email address of the current user. We had to perform a query using the WebAPI to get that, since by default you only have access to the user’s ID and Full Name within the CRM JavaScript SDK.

With that, we were able to get a token which we could use successfully to call the SharePoint REST API.

The Importance of Picking the Right Redirect URL

One final note: be careful about what URL you use for the redirect URL. Because the redirect URL is only used in the hidden iframe, the user doesn’t see it. The code also doesn’t really care what’s on that page – it’s only concerned with what’s in the query string parameters of the URL. But, if you choose poorly, things will not work.

Again, I’ve already mentioned that the URL needs to be of the same domain as the page the user is on. This is so that JavaScript code can read the properties of the DOM object. You get a security exception if you try to read properties of an iframe DOM object that is pointed at another domain.

But it’s also important that the page that you point it at doesn’t itself force a redirect. This is what we ran into when we used the root URL of our D365 as the redirect URL (for example, https://orgname.crm.dynamics.com/). This URL actually forces a redirect to https://orgname.crm.dynamics.com/main.aspx, and doesn’t maintain the query string parameters. So the code that fires when the iframe is loaded will see the page with main.aspx, and missing the query string parameter containing the all-important token. We had better results when we used the path of the web resource we were creating (for example, https://orgname.crm.dynamics.com/WebResources/new_/SharePoint.html).

10 responses to “Getting a SharePoint OAuth Access Token in a Dynamics 365 Web Resource”

  1. […] post Getting a SharePoint OAuth Access Token in a Dynamics 365 Web Resource appeared first on Engineered […]

  2. […] API requires that we pass an OAuth Bearer token as part of our request for authentication. In a previous blog post, I wrote about getting a SharePoint OAuth token within a Dynamics 365 Web Resource. The good news […]

  3. Jason Foerch says:

    Great post, and very clever. I was able to follow all the pieces that you included in your URI. The only piece that I am having a hard time identifying is what you used for the client_id. I know this would typically be the app ID of a registered App in Azure AD, but that would still require the user to first provide consent to that application. Curious how you handled this?

  4. Jason Foerch says:

    Nice blog posting and clever solution. Curious what you are using for the client_id. I know I can point it to an app registration in Azure AD, but that would require that I consent globally. Is there another way that you are doing it?

  5. Jason Foerch says:

    Thanks Nick….. that is exactly what I was looking for. Nice work again!

  6. Sumant says:

    Hi Nicholas

    Great article and something very very similar to what we are trying to do. Thanks for detailing out the finer points and hope I would have found your article a couple of months earlier to avoid all the gotchas while we tried to implement document upload to SharePoint online from Dynamics 365 via a Custom API that we are hosting within Azure.

    One thing that I find/assume and correct me if I am wrong, is different between our environments is that we are using ADFS where as you are not.

    I say so because I have tried to get a token based no the parameters that you pass to the Azure Token service, but with that token I am unable to call the SharePoint API to read/write data.

    Let me know your thoughts.

    • Nicholas Hayduk says:

      Hi Sumant,

      That’s a great question – unfortunately I don’t know the answer. It’s very possible that ADFS throws a wrench into things – all of my work was done with Azure AD. If you can figure it out, let me know and I’ll update the post.

      Nick

  7. SteveP says:

    How can this be implemented in SSIS Using Visual Studio Pro 2019 environment? The Codeplex SharePoint List Adapter no longer authenticates, now that IT has upgraded to SharePoint Online.

Leave a Reply

Your email address will not be published. Required fields are marked *

Contact

Engineered Code is a web application development firm and Microsoft Partner specializing in web portals backed by Dynamics 365 & Power Platform. Led by a professional engineer, our team of technology experts are based in Regina, Saskatchewan, Canada.