Condividi tramite


OneDrive for Business Browser Using o365 APIs

I’ve been spending a lot of time recently talking to different partners and customers about building applications using the Apps for Office and Apps for SharePoint platforms. One of the questions I seem to get most frequently though has been about one of the new additions to our development platform, which is the o365 APIs and specifically OneDrive. As I’ve been trying to field the various requests about how to use the o365 APIs for OneDrive, I decided that probably the best solution would be just to write a little browser application myself that I demonstrates using the basic features of the API and then leaving it up here for all you to download and deconstruct to your heart’s content. So…guess what I did over my Christmas break?? Yeah, that’s why I called it Christmas “break” and not “vacation”. ;-)

The other thing that I felt has been somewhat lacking from a documentation perspective is how to use ADAL generally and the o365 APIs specifically from a web app, so I decided to use that as the starting point for this application. The result is an ASP.NET MVC app that shows off the process for getting an access token that you can use with the o365 APIs and “managing” it. Once we have our access token there are a variety of different ways in which we can use it, and different options for working with OneDrive. I kind of run the gamut in the sample application – from storing and retrieving an access token in Azure table storage to passing it along as a parameter in a GET and/or POST request. In working with the o365 APIs I show a few different options: getting the whole set of content at one time on the server side and then rendering it on the client, to building it one piece at a time using jQuery with an MVC controller and partial view, to using a Web API controller. Also it does more than just list the contents of my OneDrive site – you can also create and delete folders, and upload and delete files. I also added some code in the controllers to download files for completeness, but didn’t really do anything with it in my browser (because you can just click on the link I render for the file and download it that way).

Here’s what it looks like when I actually go to my OneDrive site:

 

Here’s what my browser looks like when all of the content has been retrieved on the server side and rendered all at once on the client:

 

 

Here’s what it looks like in the view where I retrieve everything client side (and also where I do the other file management tasks):

 

Create Your Applications

To begin, before you ever start writing any code, the first thing you’re going to need to do is to create two applications in your Windows Azure Active Directory (AAD) tenant – the first one is going to be used to authenticate users into the MVC application; the second one is going to be used to access OneDrive contents through the o365 APIs. There are multiple options for both of these applications. 

Create Application for MVC Authentication

If you’re using Visual Studio 2013 then when you create a new MVC application you have the option to configure it use AAD for authentication. The process in pictures looks like this: 

 

 

Alternatively you can just go into the Azure management portal and create the application manually. This is what I did for two reasons: 1) you need to go there anyways to get the client ID and other information necessary to configure your application and 2) starting the project using the authentication wizard in Visual Studio makes it virtually impossible (or very difficult) to configure it such that you can use the same AAD authentication process to secure both your MVC and Web API controllers. So if you decide to create the application manually you just need to grant the application permission to “Enable sign-on and read users’ profiles”. Here’s what my application looks like in AAD:

 

Create Application to Access OneDrive Content

To create the second application for accessing OneDrive content, you again have a couple of choices: 1) you can install Update 4 or later for Visual Studio 2013 and select the Add Connected Service menu option when you right click on your project in Visual Studio or 2) you can create it manually. I think you get how to add it manually at this point; if you want to configure it using Visual Studio then look at the Add a New Application section of this Share-n-Dipity post: https://blogs.technet.com/b/speschka/archive/2014/12/17/using-adal-access-tokens-with-o365-rest-apis-and-csom.aspx. My completed application looks like this; you can see that I just granted the application the right to read, edit and delete users’ files:

Now that we have all the application security aspects defined we can start writing code. That being said…I’m going to preface this by saying I’m not going to do a super deep dive on all of the code in the project in this blog post. The reason for that is because a) it’s somewhat complicated, b) there’s a lot of it, and c) I’m attaching the entire project’s source code to this post. I’ll focus on conceptually what was done and why (with appropriate detail, don’t worry), but I won’t be covering every line of code like I do in some posts. You’re going to have to be a little adventurous and download the project and open it up yourself. Trust me when I say that it will be way more valuable than reading a 50 page whitepaper / blog post on using the o365 APIs. As it stands now Word is telling me that I’m already on page 10. Blech.

Working with Access Tokens in MVC

There are a few things worth noting as it relates to AAD, ADAL, access tokens and o365 APIs. The first and most important thing to remember is the every resource needs its own access token. I can’t emphasize this enough…it’s easy to get lost quickly managing your access tokens. You’ll probably start out getting an access token for the Discovery Service to get the Url for a user’s OneDrive site, and then not understand why you can’t read someone’s OneDrive files using that same access token. Well it’s because the Discovery Service and the OneDrive service are two different resources so you need to get separate access tokens for each. Now you may ask, well, I though ADAL supported multi-resource refresh tokens? Yes this is true, but that just means that you don’t have to prompt a user each time you want to access a different resource – however, you still have to go get an access token for that resource and manage it in some way.

The process becomes more complicated in a web application, which is why I wanted to take the opportunity to cover that here as well. I often do my proof of concept apps using winforms or a console app and it is genuinely easier to work through the goo that way. But a little harsh reality is always good to make sure we understand what’s required and how we can make things easier. The biggest difference is that when you are doing authentication using ADAL from a web app you need to use a client secret. This is unlike almost every other example you’ll see posted out there, which is probably why there have been so many questions about it. In a nutshell, you need to get an authorization code to access the resource, and then you can use a client ID and password (i.e. secret) to get an AuthenticationResult, which contains an access token and refresh token. Once you have the tokens then you really want to persist them somewhere, because otherwise you’ll be pestering your users non-stop to authenticate back to AAD to get a token. So given that background, here is the basic pattern that I used to get an access token and refresh token for accessing these resources:

  • Query the database to see if I have an access token for the current user and resource. In my solution I used Azure table storage to persist the tokens.

  • If there is an access token:

    • Is it valid still?

      • Yes – grab the access token

      • No – ask for a new access token using the refresh token

        • Did it return a new access token? 

          • Yes – grab the access token and update the database with the new access token and token expiration time

          • No – the refresh token has expired too so I need to start over and request a code again that I can use to get another AuthenticationResult.

  • If there is NOT an access token:

    • Ask for a code that I can use to get an AuthenticationResult
  • If I have to get a code then I:

    • Use it to get an AuthenticationResult using the AcquireTokenByAuthorizationCode method of the AuthenticationContext class; this is where I’ll pass in the client ID and password.

    • Store the access and refresh token in the database.

I built some helper classes for working with Azure so it simplifies my code somewhat, but here’s what it looks like:

 

//create an instance of the Azure storage helper

StorageHelper sh = new StorageHelper();

 

//look first for the Discovery resource token

OneDriveRequest odr = GetOneDriveToken(ref upn, DiscoveryResourceId);

 

//see if we have a record, but the token is invalid; if that's the case then we 

//need to ask for a new token using the refresh token

if (

   (!sh.IsTokenValid(odr)) &&

   (odr != null)

   )

   {

       //try and get a new access token with the refresh token;

       //if we're successful then go to the next step; otherwise

       //the refresh token is likely expired so we need to get

       //another code that can be used to get a new access token

       if (GetTokenWithRefreshToken(odr.RefreshToken, upn,

          DiscoveryResourceId, "http") != null)

          return RedirectToAction("ProcessCode", routeVals);

   }

   else if ((odr != null) && sh.IsTokenValid(odr))  //redirect cuz we have a valid token

       return RedirectToAction("ProcessCode", routeVals);

 

//if we got here, it means we didn't have an access or

//refresh token we could use

 

//create an OAuth2 request, using the web app as the client;

authorizationRequest = String.Format(

                "https://login.windows.net/common/oauth2/authorize?response_type=code&client_id={0}&resource={1}&redirect_uri={2}&state={3}",

   Uri.EscapeDataString(

       ConfigurationManager.AppSettings["o365:ClientId"]),

   Uri.EscapeDataString(DiscoveryResourceId),

   Uri.EscapeDataString(

       this.Request.Url.GetLeftPart(UriPartial.Authority).ToString() +

       "/OneDrive/ProcessCode"),

   Uri.EscapeDataString(

   RequestType.Discovery.ToString() + ";" +

   HttpUtility.UrlEncode(DiscoveryResourceId) + ";http"));

 

//return a redirect response

return new RedirectResult(authorizationRequest);

So in short, I’m looking for a valid access token – if I have one or I can get one from my refresh token then I’m going to continue onto the next step, which I do by redirecting to another action in my controller. If I don’t have a valid token then I’m going to redirect the user’s browser to the AAD oauth authorization page where they can consent to using my application. When they do that, AAD will look at the redirect_uri in the query string and send the browser back there when it’s finished; the query string will contain the code I can use to get the access token.

The other thing that may not be obvious is that I also include some information in the “state” query string. That’s because I use the same controller action to process code requests for all resources. Again, remember that every resource requires its own access token, so since I’m using both the Discovery and OneDrive services, that means I’ll be acquiring two access tokens. That’s actually how I store it in the database – the user’s UPN and the resource create the key for each row in the table. So I can ask the database for the Discovery access token for speschka@contoso.com and know that it will be separate from the access token for the OneDrive service for speschka@contoso.com.  

All roads in that first controller method lead to my ProcessCode action. That code can get complicated rather quickly if you’re just trying to grok through it in blog post so again, I’ll just walk through it more from a conceptual level. To begin with, my method signature for the method looks like this:

 

public async Task<ActionResult> ProcessCode(string code, string error, string error_description, string resource, string state)

 

Since I’m redirecting to this code whether I’ve asked for an access code or not, the first thing I do is check for whether the “code” parameter is null or empty. If it’s not, then I know that we’re responding to a request for a code that I can use to get an AuthenticationResult. What I’ll do then is this:

  • Get the client ID and password out of my web.config file. I’m going to use the client ID and password for the o365 AAD application, not the MVC AAD application.

  • I get a new AuthenticationResult by calling the AcquireTokenByAuthorizationCode on the AuthenticationContext.

  • I store the access and refresh token from my AuthenticationResult in the database.

If the “code” parameter is null or empty then I just query the database for the access token. Since I’ve passed the resource I’m trying to access in the “state” parameter when I call this action, I can extract that out now so I know what data to get from the database. 

Now that I’ve gone through that little bit of gymnastics, I’ve got my pattern in place that I can use for the rest of the app. At a high level it’s basically going to go like this:

  • Does the “state” parameter indicate that I want to access the Discovery service?

    • Yes – I’m going to create a new DiscoveryClient and use the access token I got above. After I have that I’m going to ask for information about the “MyFiles” capability. That will return two important pieces of information for me: a resource ID and an endpoint URI. The resource ID is what I’ll need to use to get an access token to access the “MyFiles” capability; the endpoint URI is where I’ll send my requests for data.

      • Once I have the ID and URI I query the database to see if I have an access token for it. It then follows the same exact pattern I described above – does it exist and is it still valid? If it exists but isn’t valid then try using the refresh token to get a new access token. If that works, or if it was already valid, then I have an access token I can use with the “MyFiles” capability (i.e. OneDrive) and I’m going to redirect to this action all over again, but this time I’m going to configure the “state” parameter to say I want to access the OneDrive service.  If I don’t have a valid access token for OneDrive, then I need to get a code for that resource that I can turn into an access token. To do that, I’m going to use virtually the same exact code I showed above, I’m just going to use the resource ID for the “MyFiles” capability instead of the Discovery Service resource ID. I’ll use the same redirect_uri so that it comes back to this same exact controller method and we start this little dance all over again.
    • No – I’m not trying to use the Discovery Service, I’m trying to use the OneDrive service (a.k.a. the “MyFiles” capability). Based on the code I just described above, at this point I know I have a valid access token for the OneDrive service. What I’m doing initially in my scenario is just getting a list of all the content in my OneDrive site, so I’ll create a new SharePointClient using my access token. I ask for the list of files, which by default just returns the root folder (“Shared with Everyone”). With that, I’ll call a method I created that gets all of the details about the folder, along with all of the folders and files it contains. In this case for each folder I find I’ll call this same method recursively so I end up with all of the folders and all of the files in my OneDrive site. When I’m done getting all of my content I plug it into the TempData dictionary that MVC provides and I redirect one more time back to my Home controller. From there it returns a view that uses the OneDrive content in the TempData dictionary and the view emits the result on the page.

That, in one ginormous nutshell, is how the first page gets loaded.

Working with Content

Now that the whole issue of dealing with access tokens is explained, most of the rest of the details are downhill. For a variety of reasons, I used both the Microsoft Office 365 Discovery and My Files Library clients, as well as the o365 REST APIs to work with the content in the OneDrive site itself. The Discovery client is really important to get started because it provides the two things you need to query OneDrive: the resource identifier for the OneDrive site, and the actual endpoint you use to work with content in that OneDrive site. In the Discovery client, you get these attributes from a CapabilityDiscoveryResult, which has ServiceResourceId and ServiceEndpointUri properties for this purpose.

With those valuable pieces of information at hand, I start out by getting the root folder of the OneDrive site by using an instance of the SharePoint client that comes with the Microsoft Office o365 libraries. It was really designed to get the access token asynchronously as part of the client creation process, but my scenario doesn’t fit into that little box because I already have an access token. Instead then, I create my client like this:

var odClient = new SharePointClient(new Uri(EndpointUri), () =>

Task.Run(() =>

{

return accessToken;

       }));

Using that I can get the root folder like this:

IPagedCollection<IItem> items = await odClient.Files.ExecuteAsync();

That collection is going to return the “Shared with Everyone” folder that is common at the root of every OneDrive site. In theory you could also have additional folders and what not at the root, but it is so rare that I chose not to focus on that for my scenario. Really all I need is the first item the collection because it has the ID for the item, and that ID is what I use to key all of my subsequent requests for data. Simply stated, I took a look at the data that’s returned by a REST call for content and I mapped that into my own class, which I call a OneDriveItem. In addition to all of the details about an item (which can be either a folder or a file), each OneDriveItem also has a List<OneDriveItem> of Folders and Files. The content schema is the same for both, so this is why OneDriveItem is used for everything in my code sample.

You can probably imagine the rest of how that scenario plays out (or just download the code to look at it) – I have my item ID, so I a) get all of the details for that item, b) get all of the files contained in that item and c) get all of the folders contained in that item. For each folder I find, I can optionally call the same method recursively to get all of the details, files and folders in it. That’s really the main difference between the view that shows the entire contents of the OneDrive site at load time, and the view that loads it one folder at a time; when I show everything I call the method recursively and when I show it one folder at a time I do not.

Working with Content from Client Script

At this point I’ve explained how I get the access token and how I retrieve contents on the server side. Working with data from client script involved using jQuery and calling either a standard MVC controller (when I want a chunk of HTML back) or a Web API controller (when I want to perform some action, like adding or deleting a folder). I chose to write the access token to a hidden field on the page and send it back to the controller either in a query string or HTML form when I requested data. Since everything is working over SSL I’m okay with that. If you are not, another alternative could be to store some key to your database where you keep the access token, send that up, and look it up. There are a few minor nuances in terms of what it means from a security standpoint, but I’ll leave it up to you and your requirements to dictate the appropriate implementation. 

That aside, the implementation for this sample was fairly straightforward. When I needed HTML I called an MVC controller action because I can return a view from that, which returns HTML. In fact I ended up using the same exact partial view to render both the view where I showed the entire contents all at once, and the view where I build the contents one folder at a time.

In addition to rendering contents, on the client view you can also add folders, delete folders, upload files, and delete files. I also wrote methods in the controllers to download files but didn’t actually implement then on the client script view. The reason for that is because I rendered each file as a hyperlink to that file, so you can just click on the file to open it up. I added the code in the controller though in case you are working with some other modality (like a console app, or winforms app, or whatever). Fortunately both the REST endpoint and Office 365 libraries make those other functions pretty straightforward to do. Here’s how to create a new folder for example; the parameter NewFolderRequest is just a custom class I created that contains all of the HTML form values that were submitted with the request. The return value from creating a folder is the same set of JSON that you get when you query the folder details, so I’m able to deserialize that back into a OneDriveItem again so it plugs into my various rendering methods really well.

public static async Task<OneDriveItem> CreateFolder(NewFolderRequest nfr)

{

   //PUT {base-url}/Files/{parent-id}/children/{folder-name}

   //from https://msdn.microsoft.com/office/office365/APi/

   //files-rest-operations#FolderresourceCreateafolder

 

   OneDriveItem result = null;

   try

   {

       //create the Url for the new folder

       string requestUrl = nfr.endpointUri + "/files/" + nfr.parentFolder +

          "/children/" + nfr.newFolder;

 

       HttpClient hc = new HttpClient();

 

       //add the header with the access token

       hc.DefaultRequestHeaders.Authorization = new

          System.Net.Http.Headers.AuthenticationHeaderValue(

          "Bearer", nfr.accessToken);

 

       //make the put request

       HttpResponseMessage hrm =

          (await hc.PutAsync(requestUrl, new StringContent("")));

 

       if (hrm.IsSuccessStatusCode)

       {

          string data = await hrm.Content.ReadAsStringAsync();

          result = OneDriveItem.GetFromJSON(data);

       }

   }

   catch (Exception ex)

   {

       Debug.WriteLine(ex.Message);

   }

   return result;

}

The gist is pretty simple really – I create a Url in the format that the o365 REST endpoint requires for creating a new folder. I create a new HttpClient and add the authorization header using the access token I have. Then I make the PUT request to create the new folder. If it works, I take the JSON I get back and deserialize it into a OneDriveItem that I can then send back so it can be rendered by the client script into the view of the contents I’ve built in the page. The other commands for adding and deleting items are pretty similar, so you can take a look at the code included with this post for specific details.

Summary

You have what should be a nice pattern now for working with o365 access tokens in an MVC app. In addition the solution includes working examples of the most common tasks when using the o365 APIs for OneDrive. It also demonstrates using both the o365 libraries as well as the REST endpoints for working with data in OneDrive sites. Using this sample as a reference should provide you with enough examples to get any of your o365 API applications up and running for OneDrive sites.

OneDriveBrowser.zip

Comments

  • Anonymous
    January 19, 2015
    In this post I’m going to briefly cover a custom token cache that I wrote for use with ADAL. The