Integrating AngularJS with Azure Active Directory Services and Office 365/SharePoint, Part 4

Adding AAD and OWIN code to handle user authentication

Dan Wahlin

by Dan Wahlin on 12/31/2014

Share this:
Print

Article Details

Date Revised:
12/31/2014

Applies to:
AAD, AngularJS, Azure Active Directory, Dan Wahlin, Office 365, SharePoint Online


Sponsored by


Part 3 of this series covered how to access the Client ID, Key, and Tenant ID values from Azure Active Directory (AAD) and add them into web.config. It also showed how to get the necessary AAD and OWIN NuGet packages in place and create a SettingsHelper class to simplify the process of accessing web.config values.

In this article, you’ll see how the values defined in web.config can be used to associate a custom application with AAD. Topics covered include setting up a token storage cache, hooking AAD code into the OWIN startup process, and creating an ASP.NET MVC controller to display a login page to end users and handle directing them to the application upon successful authentication. All of the code that follows is from the Expense Manager application that’s available on the OfficeDev GitHub site.

Let’s kick things off by looking at AAD token storage and the role it plays in applications.

AAD token storage

The NuGet packages added into the application (see Part 3 of this series) provide the necessary functionality to authenticate a user with AAD. Once authenticated, AAD will return an ID token that can be stored by the application and used as secured resources such as Web API, Office 365 APIs, or other resources are accessed. The AAD documentation provides a diagram that sums up the overall authentication workflow well:

The flow as a user logs into AAD, gets an ID token, and then accesses an application and additional resources

Figure 1. The flow as a user logs into AAD, gets an ID token, and then accesses an application and additional resources. This image is from http://msdn.microsoft.com/en-us/library/azure/dn499820.aspx.

While the token received by the application can be stored in memory during development, it’s recommended that a more robust token store be put in place to handle that task for production. You can find several sample applications that integrate with AAD and handle tokens on the Azure Active Directory Github samples site. Some of the samples use an in-memory store while others rely on a database, which is recommended when an app is ready to move to production.

In this part of the article, you’ll see the steps required to get an Entity Framework and SQL Server token store in place. If you don’t already have the Entity Framework NuGet package installed in your project you’ll need to install it.

From a high level, the following tasks will be discussed:

  1. Create a model class named PerWebUserCache that’s used to define properties that are needed to store and retrieve AAD tokens.
  2. Create an Entity Framework DbContext class named EFADALContext that interacts with a SQL Server database.
  3. Create a token cache class named EFADALTokenCache that uses the context class to store and retrieve tokens from a SQL Server database.
  4. Add startup code that uses the previous classes and is responsible for handling authentication as a user requests a secure resource.

To get started, add a new class named PerWebUserCache.cs into the Models folder of an ASP.NET MVC application. This code defines properties that will be used by the token store. Add the following code into the class:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;

namespace ExpenseManager.Models
{
   public class PerWebUserCache
   {
       [Key]
       public int EntryId { get; set; }
       public string webUserUniqueId { get; set; }
       public byte[] cacheBits { get; set; }
       public DateTime LastWrite { get; set; }
   }
}

Listing 1. The PerWebUserCache model class defines properties that will be used by the token store.

Now add a class named EFADALContext.cs into the Utils folder. This class will derive from Entity Framework’s DbContext class and handle mapping the PerWebUserCache object to the proper table in SQL Server. Add the following code into the EFADALContext class:

using ExpenseManager.Models;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;
using System.Web;

namespace ExpenseManager.Utils
{
   public class EFADALContext : DbContext
   {
       public EFADALContext() : base("EFADALContext")
       {
       }
       public DbSet<PerWebUserCache> PerUserCacheList { get; set; }
       protected override void OnModelCreating(DbModelBuilder modelBuilder)
       {
           modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
       }
   }
}

Listing 2. The EFADALContext class handles table creation and queries with SQL Server.

Now that the model and database context classes have been created, a token cache class named EFADALTokenCache can be created that uses the context to store and retrieve AAD tokens. This is one of many classes provided by the AAD samples site on GitHub that was mentioned earlier.

Add a class named EFADALTokenCache.cs into the Utils folder that has the following code in it:

using Microsoft.IdentityModel.Clients.ActiveDirectory;
using ExpenseManager.Models;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;

namespace ExpenseManager.Utils
{
   public class EFADALTokenCache : TokenCache
   {
       private EFADALContext _Context = new EFADALContext();
       string User;
       PerWebUserCache Cache;

       // constructor
       public EFADALTokenCache(string user)
       {
           // associate the cache to the current user of the web app
           User = user;
           this.AfterAccess = AfterAccessNotification;
           this.BeforeAccess = BeforeAccessNotification;
           this.BeforeWrite = BeforeWriteNotification;

           // look up the entry in the DB
           Cache = _Context.PerUserCacheList.FirstOrDefault(c => 
                    c.webUserUniqueId == User);
           // place the entry in memory
           this.Deserialize((Cache == null) ? null : Cache.cacheBits);
       }

       // clean up the DB
       public override void Clear()
       {
           base.Clear();
           foreach (var cacheEntry in _Context.PerUserCacheList)
               _Context.PerUserCacheList.Remove(cacheEntry);
           _Context.SaveChanges();
       }

       // Notification raised before ADAL accesses the cache.
       // This is your chance to update the in-memory copy from the DB
       // if the in-memory version is stale.
       void BeforeAccessNotification(TokenCacheNotificationArgs args)
       {
           if (Cache == null)
           {
               // first time access
               Cache = _Context.PerUserCacheList.FirstOrDefault(c => 
                        c.webUserUniqueId == User);
           }
           else
           {   // retrieve last write from the DB
               var status = from e in _Context.PerUserCacheList
                            where (e.webUserUniqueId == User)
                            select new
                            {
                                LastWrite = e.LastWrite
                            };
               // if the in-memory copy is older than the persistent copy
               if (status.First().LastWrite > Cache.LastWrite)

               // read from from storage, update in-memory copy
               {
                   Cache = _Context.PerUserCacheList.FirstOrDefault(
                            c => c.webUserUniqueId == User);
               }
           }
           this.Deserialize((Cache == null) ? null : Cache.cacheBits);
       }

       // Notification raised after ADAL accessed the cache.
       // If the HasStateChanged flag is set, ADAL changed the content of the cache
       void AfterAccessNotification(TokenCacheNotificationArgs args)
       {
           // if state changed
           if (this.HasStateChanged)
           {
               Cache = new PerWebUserCache
               {
                   webUserUniqueId = User,
                   cacheBits = this.Serialize(),
                   LastWrite = DateTime.Now
               };

               // update the DB and the lastwrite                
               _Context.Entry(Cache).State = Cache.EntryId == 0 ? 
                  EntityState.Added : EntityState.Modified;
               _Context.SaveChanges();
               this.HasStateChanged = false;
           }
       }
       void BeforeWriteNotification(TokenCacheNotificationArgs args)
       {
           // if you want to ensure that no concurrent write takes place, 
           // use this notification to place a lock on the entry
       }
   }
}

Listing 3. The EFADALTokenCache class is responsible for storing and retrieving AAD tokens used by the application.

This code handles managing an in-memory store that is backed by a database. As changes are made or the local cache becomes stale, calls are made to update the proper fields in the database. With the EFADALTokenCache class in place along with the model and database context classes, it’s now time to add AAD code in the application to allow users to be authenticated. This code will tie AAD into OWIN so that any secured application resources trigger the authentication process.

AAD authentication code

The application now has AAD values configured in web.config and token cache code in place but nothing has been added to handle authenticating the user. This functionality is handled by a class named Startup.cs (and a related partial class) that hooks into OWIN.

Add a new class named Startup.cs into the root of the project with the following code in it:

using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(ExpenseManager.Startup))]   

namespace ExpenseManager
{     
   public partial class Startup     
   {         
       public void Configuration(IAppBuilder app)         
       {             
           ConfigureAuth(app);         
       }    
   }
}

Listing 4. The Startup class handles integrating AAD authentication with OWIN.

The attribute added above the namespace causes the Startup class to be called as OWIN starts up. The Configuration() method that’s called passes OWIN’s IAppBuilder object (app in this example) into another method named ConfigureAuth(). You’ll notice that ConfigureAuth() is missing from the Startup class though.

To fix this problem, add another class named Startup.Auth.cs into the App_Start folder in the project with the following code in it. Note that this class is a partial class that contains the ConfigureAuth() method in it.

using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
using ExpenseManager.Utils;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;
using System.Web.Configuration;

namespace ExpenseManager
{
   public partial class Startup
   {
       public void ConfigureAuth(IAppBuilder app)
       {
           app.SetDefaultSignInAsAuthenticationType(
                CookieAuthenticationDefaults.AuthenticationType);
           app.UseCookieAuthentication(new CookieAuthenticationOptions());
           app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
           {
               ClientId = SettingsHelper.ClientId,
               Authority = SettingsHelper.AzureADAuthority,
               Notifications = new OpenIdConnectAuthenticationNotifications()
               {
                   AuthorizationCodeReceived = (context) =>
                   {
                       var code = context.Code;
                       var creds = new ClientCredential(
                         SettingsHelper.ClientId, SettingsHelper.ClientSecret);
                       var userObjectId = 
                         context.AuthenticationTicket.Identity.FindFirst(
                          ClaimTypes.NameIdentifier).Value;
                       var tokenCache = new EFADALTokenCache(userObjectId);
                       var authContext = new AuthenticationContext(
                          SettingsHelper.AzureADAuthority, tokenCache);
                       var redirectUri = new Uri(
                          HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));

                       //Not using authResult variable here but can be useful
                       //for debugging purposes
                       var authResult = authContext.AcquireTokenByAuthorizationCode(
                           code, redirectUri, creds, SettingsHelper.GraphResourceId);

                       return Task.FromResult(0);
                   },
                   AuthenticationFailed = (context) =>
                   {
                       context.HandleResponse();
                       return Task.FromResult(0);
                   }
         }
           });
       }
   }
}

Listing 5. Tying AAD authentication into the application using the AuthenticationContext class.

There’s quite a bit going on in this class, so let’s break down the key tasks that are performed:

  1. The EFADALTokenCache class derives from TokenCache, which is a class provided by AAD that’s located in the Microsoft.IdentityModel.Clients.ActiveDirectory namespace.
  2. The code adds the ConfigureAuth() method that is called when OWIN starts up.
  3. Cookie authentication is setup to store necessary values securely on the client as the initial session begins after a user logs in (see Figure 1 above).
  4. The ClientId and AzureADAuthority properties created earlier are accessed using the SettingsHelper class (covered in Part 3 of this series) and assigned to properties in the code.
  5. Several objects are created such as ClientCredential and EFADALTokenCache in the AuthorizationCodeReceived() method.
  6. An AuthenticationContext object is created and the AzureADAuthority value and token cache object are passed into it’s constructor. This is one of the key objects in this code.
  7. The AuthenticationContext object is used to acquire an authorization token from AAD.
  8. The AuthenticationResult object returned from calling AcquireTokenByAuthorizationCode() method contains the access token, token expiration time, and a refresh token.

While there’s a lot of code to look through in Listing 5, it all boils down to redirecting the user to an AAD login screen if they haven’t already logged in and accessing and storing the token that’s received if they successfully authenticate. As they try to call a secured resource (Web API, Office 365, or another), the authorization token that’s received will be used to gain access. While it takes some work to get this code in place, once you get it working it’s fairly straightforward to migrate it to other applications.

Adding an MVC controller with login functionality

So how does the AAD code get triggered so that a user can login to the application? There are several ways to do it, actually. The Expense Manager application sends users to a Login screen that displays a Login button. Users could be redirected to the AAD login screen immediately upon hitting the application as well.

Expense Manager application login screen

Figure 2. The Login screen.

Once a user clicks the Login button, they’ll be redirected to the AAD login screen where they can enter their credentials. Upon successful authentication, AAD will then redirect them back to the application while passing an ID token and an auth code.

An MVC controller named HomeController (located in the Controllers folder in the Expense Manager application) handles kicking off the authentication process:

using ExpenseManager.SharePointHelpers;
using ExpenseManager.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;

namespace ExpenseManager.Controllers
{
   [Authorize]
   public class HomeController : Controller
   {
       [AllowAnonymous]
       public ActionResult Index()
       {
           ViewBag.Title = "Login";
           return View();
       }

       //Will trigger AD authentication due to Authorize attribute on the controller
       public ActionResult Login()
       {
           return new RedirectResult("/index.html");
       }
   }
}

Listing 6. The HomeController handles securing the application using the Authorize attribute.

The controller has the [Authorize] attribute placed on top of it to secure the actions within it. However, the Index() action within the controller can be accessed anonymously, since it has the [AllowAnonymous] attribute, and handles serving up the Login page shown earlier.

The Login button uses the following HTML to call the controller’s Login() action:

<a href="/home/login" class="btn-lg btn-primary center-block text-center">Login</a>

Listing 7. Calling the HomeController’s Login() action to trigger AAD authentication.

Once a user successfully logs in to AAD, they’ll be redirected back to the application (the redirect URL is defined when the application is initially registered with AAD) which sends them to a page named index.html. The index.html page triggers the AngularJS Single Page Application (SPA) functionality and loads the default view. If the user tries to bypass the login and go directly to index.html, no data from Office 365 will be loaded since the proper token won’t exist yet.

Conclusion

In Part 4 of this article series you’ve learned about the code that’s required to create a token cache and hook AAD into OWIN’s startup routine. Now that authentication functionality is in place for the application, we can begin discussing how to call Office 365 APIs. That is the subject of the next article.


Topic: Development

Sign in with

Or register