Office 365 Authentication using Visual Studio MVC application

Let’s see how we can access Office 365 resources from a standard MVC application which could be hosted on any web server


Lets create an MVC application which has it’s back end as office 365. In order to try this out, you just need to have Visual Studio installed on your machine and Office 365 account.

Lets start by creating a new project.

  1. Select Web -> ASP.Net Web Application and click OK
    Office 365 Authentication Office365Authentication-NewProject
  2. Click on Change Authentication
    Office 365 Authentication ChangeAuthentication
  3. Select No Authentication. Click OK.
    Office 365 Authentication NoAuth

Setting up the tools to connect to Office 365

Lets first install the Office 365 API Tools which is required for connecting to the Office 365 API.

  1. In Visual Studio, go to Tools/Extensions
  2. Search for Office 365
  3. Install the Office 365 API Tools

Next, lets add a connected service

  1. Right click on the project -> Add -> Connected Service
    Office 365 Authentication AddConnectedService
  2. Click on Register your app
    RegisterApp
  3. When prompted, sign into your Office 365 account
  4. We need to set permissions here. Since we need to read from Contacts, click on Contacts -> permissions , check ‘Read your contacts’, click Apply
    Office 365 Authentication Permissions
  5. Click on Users and Groups and check ‘Sign you in and read your profile
    Office 365 Authentication UserProfilePermission
  6. Click on app properties and remove the http address, click Apply and OK
    Office 365 Authentication AppProperties

This will install the required components including the Outlook services and Discovery services which we will be using to retrieve information from Office 365.

Configuring the authentication

Setting the https endpoint

  1. In the project properties, set SSL Enabled to True
  2. Copy the SSL URL property
  3. Right-click the project, and select Properties -> Web tab -> in Servers section -> set Project URL to the SSL URL created above

We need to install some NuGet packages to the project. Right click on the project and click on Manage NuGet packages.
Office 365 Authentication Manage-NuGet

Ensure you have selected online on the left, search and install the following NuGet packages:

  1. Active Directory Authentication Library
  2. EntityFramework
  3. Microsoft.Owin.Host.SystemWeb
  4. Microsoft.Owin.Security.Cookies
  5. Microsoft.Owin.Security.OpenIdConnect

Configure the Token Cache database

  1. Right click on App_Data and select Add -> New Item.
  2. Name the db ADALTokenCacheDb

Update the web.config to reflect the db name

<connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=(LocalDB)\v11.0;AttachDbFilename=|DataDirectory|\ADALTokenCacheDb.mdf;Integrated Security=True" providerName="System.Data.SqlClient" />
</connectionStrings>

  1. Right click on the project and select Add New-> OWIN startup class.
    Enter name of the file as Startup.cs (This is found under Web -> General)
  2. Add the following namespace references (Update your namespace for Models and Utils):

    using Microsoft.IdentityModel.Clients.ActiveDirectory;
    using Microsoft.Owin.Security;
    using Microsoft.Owin.Security.Cookies;
    using Microsoft.Owin.Security.OpenIdConnect;
    using Office365Authentication.Models;
    using Office365Authentication.Utils;
    using Owin;
    using System;
    using System.IdentityModel.Claims;
    using System.Threading.Tasks;
    using System.Web;
    using Microsoft.Owin;
    

  3. Add the following method

    public void ConfigureAuth(IAppBuilder app)
    {
      app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
    
      app.UseCookieAuthentication(new CookieAuthenticationOptions());
    
      app.UseOpenIdConnectAuthentication(
          new OpenIdConnectAuthenticationOptions
          {
            ClientId = SettingsHelper.ClientId,
            Authority = SettingsHelper.Authority,
    
            Notifications = new OpenIdConnectAuthenticationNotifications()
            {
              // If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
              AuthorizationCodeReceived = (context) =>
              {
                var code = context.Code;
                ClientCredential credential = new ClientCredential(SettingsHelper.ClientId, SettingsHelper.AppKey);
                String signInUserId = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
    
                AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.Authority, new ADALTokenCache(signInUserId));
                AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, SettingsHelper.AADGraphResourceId);
    
                return Task.FromResult(0);
              },
              RedirectToIdentityProvider = (context) =>
              {
                // This ensures that the address used for sign in and sign out is picked up dynamically from the request
                // this allows you to deploy your app (to Azure Web Sites, for example)without having to change settings
                // Remember that the base URL of the address used here must be provisioned in Azure AD beforehand.
                string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
                context.ProtocolMessage.RedirectUri = appBaseUrl + "/";
                context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
    
                return Task.FromResult(0);
              },
              AuthenticationFailed = (context) =>
              {
                // Suppress the exception if you don't want to see the error
                context.HandleResponse();
                return Task.FromResult(0);
              }
            }
    
          });
    }
    

  4. Call this method from Startup.Configuration as follows:

    public void Configuration(IAppBuilder app)
    {
      ConfigureAuth(app);
    }
    

Let’s use part of the code in the github project Office 365 single-tenant MVC project.

  1. Copy the SettingsHelper.cs into the Utils folder
  2. Copy the ApplicationDbContext.cs and the ADALTokenCache.cs to the Models folder
  3. Copy the _LoginPartial.cshtml file into the Views > Shared folder
  4. Ensure that you update the namespace in the copied files

Now lets add the sign in and sign out functionality

  1. Right click on Controllers folder and add a blank controller called AccountController. Replace the existing AccountController
  2. Replace the namespace with

    using Microsoft.Owin.Security;
    using Microsoft.Owin.Security.Cookies;
    using Microsoft.Owin.Security.OpenIdConnect;
    using System.Web;
    using System.Web.Mvc;
    

  3. Delete the index method and add the following:

    public void SignIn()
    {
      if (!Request.IsAuthenticated)
      {
        HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
      }
    }
    public void SignOut()
    {
      string callbackUrl = Url.Action("SignOutCallback", "Account", routeValues: null, protocol: Request.Url.Scheme);
    
      HttpContext.GetOwinContext().Authentication.SignOut(
          new AuthenticationProperties { RedirectUri = callbackUrl },
          OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
    }
    
    public ActionResult SignOutCallback()
    {
      if (Request.IsAuthenticated)
      {
        // Redirect to home page if the user is authenticated.
        return RedirectToAction("Index", "Home");
      }
    
      return View();
    }
    

Update the MVC application to retrieve and show the contact information from Office 365

Now, lets update the MVC application to retrive the contact information.

  1. Add a class to the Models folder called MyContactUpdate the code as

    public class MyContact
    {
      public string Name { get; set; }
    }
    

  2. Add an empty controller MVC 5 Controller – Empty to the Controllers folder called ContactsController
  3. Add the usings (Update your project namespace for Models and Utils)

    using Microsoft.IdentityModel.Clients.ActiveDirectory;
    using Microsoft.Office365.Discovery;
    using Microsoft.Office365.OutlookServices;
    using Office365Authentication.Models;
    using Office365Authentication.Utils;
    using System.Collections.Generic;
    using System.Security.Claims;
    using System.Threading.Tasks;
    using System.Web.Mvc;
    

  4. Add an authorize attribute

    [Authorize]
    public class ContactsController : Controller
    

  5. Change the index method to aysnchronous

    public async Task<ActionResult> Index()
    

  6. Add the following code to index method

    List<MyContact> myContacts = new List<MyContact>();
    
    var signInUserId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
    var userObjectId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
    
    AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.Authority, new ADALTokenCache(signInUserId));
    try
    {
      DiscoveryClient discClient = new DiscoveryClient(SettingsHelper.DiscoveryServiceEndpointUri,
        async () =>
        {
          var authResult = await authContext.AcquireTokenSilentAsync(SettingsHelper.DiscoveryServiceResourceId, new ClientCredential(SettingsHelper.ClientId, SettingsHelper.AppKey), new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
    
          return authResult.AccessToken;
        });
    
      var dcr = await discClient.DiscoverCapabilityAsync("Contacts");
    
      OutlookServicesClient exClient = new OutlookServicesClient(dcr.ServiceEndpointUri,
        async () =>
        {
          var authResult = await authContext.AcquireTokenSilentAsync(dcr.ServiceResourceId, new ClientCredential(SettingsHelper.ClientId, SettingsHelper.AppKey), new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
    
          return authResult.AccessToken;
        });
    
      var contactsResult = await exClient.Me.Contacts.ExecuteAsync();
    
      do
      {
        var contacts = contactsResult.CurrentPage;
        foreach (var contact in contacts)
        {
          myContacts.Add(new MyContact { Name = contact.DisplayName });
        }
    
        contactsResult = await contactsResult.GetNextPageAsync();
    
      } while (contactsResult != null);
    }
    catch (AdalException exception)
    {
      //handle token acquisition failure
      if (exception.ErrorCode == AdalError.FailedToAcquireTokenSilently)
      {
        authContext.TokenCache.Clear();
        //handle token acquisition failure
      }
    }
    
    return View(myContacts);
    

Now, lets add the view to the Contacts controller:

  1. In the Views folder, right click on Contacts folder -> Add View
  2. Enter view name as Index
  3. Select List as the template
  4. Select MyContact as model
  5. Click on Add

In the Shared folder, open the _Layout.cshtml file

  1. Add an actionlink to the MyContacts page to get a navigation link to the MyContacts view so that it is updated as:

    
    
    <div class="navbar-collapse collapse">
    
    <ul class="nav navbar-nav">
    
    <li>@Html.ActionLink("Home", "Index", "Home")</li>
    
    
    <li>@Html.ActionLink("About", "About", "Home")</li>
    
    
    <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
    
    
    <li>@Html.ActionLink("My Contacts", "Index", "Contacts")</li>
    
      </ul>
    
      @Html.Partial("_LoginPartial")
    </div>
    
    

Now if you run the application, and click on the MyContacts link, it should show the contacts in your Office 365 site!

When prompted, login to your Office 365 account and accept the permission requirements.
Office 365 Authentication Output

Leave a Reply

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