Partager via


OneDriveBrowser and o365 APIs with A Custom Persistent Store for ADAL Token Cache

In this post I’m going to briefly cover a custom token cache that I wrote for use with ADAL. The implementation itself is pretty straightforward from a coding perspective so I will just highlight a few of the basics. What has been less clear up to this point is the “right way” to use it in your apps to make sure that you’re cached tokens are being used correctly, so I’ll cover that as well. The token cache code itself is based on the general outline of the capabilities as Vittorio described in this blog post:  https://www.cloudidentity.com/blog/2014/07/09/the-new-token-cache-in-adal-v2/. The sample application I’m using is an updated version of the OneDriveBrowser built on the o365 APIs that I originally covered here:  https://blogs.technet.com/b/speschka/archive/2015/01/05/onedrive-for-business-browser-using-o365-apis.aspx.

Coding Implementation

The code itself is pretty straightforward. To begin with I created a new project and added the ADAL v2 Nuget package. I then created a new class and had it inherit from Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache. In my implementation I’m storing the tokens in Azure table storage. I’m using it in an ASP.NET MVC application, and I need to track the tokens on a per user basis. In my case the main customizations then that I need to account for are a) tracking tokens for individual users and b) managing access to Azure storage. I provide hooks to both of those things through my constructor.

For tracking individual users I’ve used the user UPN. I’ve configured my MVC app so that it’s secured with Azure Active Directory. Since a user has to authenticate before they even get into my web app, I can be assured that they have a UPN. For managing access to Azure storage I left in a couple of options. I have the storage name and key included in one of the helper classes in my custom TokenCache project. That way if you just want to take the code and compile it yourself, you can replace those values with the storage name and key that you are using. As another option, I created a second constructor that takes the storage name and key in addition to UPN so you can set your credentials on the fly. Here’s an example of what the two constructors look like (my class name is TokenStore):

 

public TokenStore(string UPN)

public TokenStore(string UPN, string storageName, string storagePassword)

 

Inside the class I’ve implemented BeforeAccessNotification, AfterAccessNotification, and DeleteItem. The notifications are hooked up from my constructors with code like this:

this.BeforeAccess = BeforeAccessNotification;

this.AfterAccess = AfterAccessNotification;

 

DeleteItem doesn’t have an event handler to hook to so in that case I just have to override it, invoke the base class method first, then do my stuff. Again, the actual code for all of this is pretty simple. I created a helper class to manage all of the Azure stuff, so in pseudo code terms my implementations look like this:

  • BeforeAccess: use the UPN that was passed into the constructor and the resource ID that is being requested. Look in Azure storage to see if I have a token in there for that combination; if so, then get the byte array out of table storage and call the Deserialize method that comes from the base class.

  • AfterAccess: look to see if the cache was updated (for example, if the access token was refreshed). If so, save the byte array back to table storage and set the HasStateChanged property of my token cache to false so that it doesn’t keep trying to overwrite the same token.

  • DeleteItem: call the base class so the deleted token is removed from the in-memory cache, then delete the same item out of Azure table storage.

 

That’s basically it. All of the source code is included with this post so you can review it and use it as you see fit.

App Implementation

As I mentioned above the trickier part in using ADAL with my custom token cache was to ensure that I was using ADAL “the right way” in my app. Arguably this really isn’t even specific to my custom token cache, it’s the way that I should be using ADAL anywhere, regardless of what caching mechanism is being used for it. As I was describing before I’m using this with an ASP.NET app; that means that in order to get an access token I need to use the acquire token method in ADAL that uses an authorization code and client credential (so I can pass in the app secret). What’s important in this process as well though (according to the Vittorio blog I referenced at the beginning) is that you use the user’s actual login Authority when you create the AuthenticationContext, versus just using common (i.e. https://login.windows.net/common/). The last part of the flow is that when a user is ready to get an AuthenticationResult in subsequent requests, you should no longer ask for it using the authorization code – instead you should try one of the AcquireTokenSilent overloads.

So with all of this information in hand, here’s how the process works of getting an AuthenticationResult (which has the access token):

  • I have a table in Azure storage where I keep track of each user’s authentication parameters – UPN, resource ID, and login Authority. When the user goes into a controller action that needs an access token I look in that table to see if I have a record matching the UPN and resource they are requesting.

  • The first time a user logs in, there won’t be any record so I redirect out to Azure to get an authorization code.

  • When Azure redirects to my controller, I see that there is an authorization code so I:

    • Create a new instance of AuthenticationContext, using my custom token cache: authContext = new AuthenticationContext("https://login.windows.net/common/", new AdalAzureCache.TokenStore(upn));

    • Request an AuthenticationResult: result = authContext.AcquireTokenByAuthorizationCode(AuthCode, new Uri(Request.Url.GetLeftPart(UriPartial.Path)), credential);

    • Store the user’s UPN, the ID of the resource they were requesting, and the login Authority in my custom storage for this app. To be clear – this is storage just for this app – it is different from the storage that my custom token cache class uses. The way I get the Authority for the user is after I’ve acquired an AuthenticationResult for them, the AuthenticationContext.Authority property has their specific Authority.

  • The next time the user needs an AuthenticationResult, I look in storage for my app and I find a record with the user’s UPN, resource and Authority. In that case I:

    • Create a new instance of the AuthenticationContext; same as before but this time I use the user’s Authority: authContext = new AuthenticationContext("user’s authority here", new AdalAzureCache.TokenStore(upn));

    • Request an AuthenticationResult silently: result = authContext.AcquireTokenSilent(ResourceUri, credential, UserIdentifier.AnyUser); This particular overload is one that I just found through trial and error to work with my token cache.

That’s basically it. When the request to acquire the token silently is called, I see my custom token cache getting invoked. It finds the token cache in Azure table storage for the user and resource and then sends it out for ADAL to use. Everything just plugs in and out like a well-oiled machine and works. 

All of the source code for the OneDriveBrowser application that has been updated to use the new App Implementation described above and has been included, as well as the custom ADAL token cache that uses Azure table storage. As with the previous OneDriveBrowser post, you will need to go into the Globals.cs in the AdalAzureCache and CloudHelper projects and plug in your Azure storage name and key (exception: if you don’t want to compile it in AdalAzureCache you can pass the values in through the constructor as explained previously). In the web.config file for the OneDriveBrowser MVC application you will need to go plug in your Azure application values. Hope you find it useful.

OneDriveBrowser_WithAdalTokenCache.zip

Comments

  • Anonymous
    February 01, 2015
    Un mes más, en este post os dejo un nuevo recopilatorio de recursos y enlaces interesantes sobre plataformas