Episerver with Azure AD authentication

In this post, I will go through the steps I took to disable the built-in membership provider of Episerver and instead use Azure’s Active Directory authentication.

Register your Episerver app within your Azure Active Directory (AD)

You will first need to register your app on the Azure AD. Follow this post I created before proceeding to the next steps below.

Setup the Episerver groups in Azure

In this blog I am assuming your users are already setup in Azure. If not, creating the users (either via Domain Controller or on Azure itself) should be as easy as creating the groups. Either way, you will need to setup the Episerver groups:

  • WebAdmins
  • WebEditors
  • WebPublisher
  • WebView

As these groups are by default setup in Episerver, the permissions will automatically be bound when a user from any of these groups login. For example, a user who is assigned to “WebEditors” in Azure, will be able to login to the Episerver site and edit content. Which means, there is no need to set up permissions in Azure as these permissions are setup in Episerver.

  1. Azure Active Directory > Users and groups > All groups > + New group
  2. Enter details and assign the relevant users to this group
  3. Repeat steps 1&2 for all four Episerver groups mentioned above

Configure your Episerver site to trust Azure’s Identity Provider (Active Directory)

  1. On your web.config, replace your membershipProvider and roleManager tags:
    • <membership><providers><clear /></providers></membership>
    • <roleManager enabled=”false”><providers><clear /></providers></roleManager>
  2. On your web.config, add the following keys in the appSettings. The values below should come from the Azure details I have mentioned above in the exact same order.
    • <add key=”MetadataAddress” value=”” />
    • <add key=”Wtrealm” value=”” />
    • <add key=”TenantName” value=”XXX.onmicrosoft.com” />
    • <add key=”ClientId” value=”” />
    • <add key=”ClientSecret” value=”” />
    • <add key=”GraphUrl” value=”https://graph.windows.net&#8221; />
  3. Add/Edit your Startup.cs class with the following:
[assembly: OwinStartup(typeof(Startup))]
namespace Nicola.Core { 
   public class Startup { 
      private const string LogoutUrl = "/util/logout.aspx";
      public void Configuration(IAppBuilder app) { 
         // Enable cookie authentication, used to store the claims between requests 
         app.SetDefaultSignInAsAuthenticationType(
            WsFederationAuthenticationDefaults.AuthenticationType); 
         app.UseCookieAuthentication(new CookieAuthenticationOptions { 
            AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType 
         }); 

         // Enable federated authentication 
         app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions { 
            // Trusted URL to federation server meta data 
            MetadataAddress = ConfigurationManager.AppSettings["MetadataAddress"], 
            // Value of Wtreal must *exactly* match what is configured in the federation server 
            Wtrealm = ConfigurationManager.AppSettings["Wtrealm"], 
            Notifications = new WsFederationAuthenticationNotifications { 
               RedirectToIdentityProvider = ctx => { 
                  //  To avoid a redirect loop to the federation server send 403 when user is authenticated but does not have access 
                  if (ctx.OwinContext.Response.StatusCode == 401 
                     && ctx.OwinContext.Authentication.User.Identity.IsAuthenticated) { 
                     ctx.OwinContext.Response.StatusCode = 403; 
                     ctx.HandleResponse(); 
                  } 
                  return Task.FromResult(0); 
               }, 
               SecurityTokenValidated = async ctx => { 
                  // Ignore scheme/host name in redirect Uri to make sure a redirect to HTTPS does not redirect back to HTTP 
                  var redirectUri = new Uri(ctx.AuthenticationTicket.Properties.RedirectUri, 
                     UriKind.RelativeOrAbsolute); 
                  if (redirectUri.IsAbsoluteUri) 
                     ctx.AuthenticationTicket.Properties.RedirectUri = redirectUri.PathAndQuery;
                  
                  #region Azure
                  // Create claims for roles await 
                  ServiceLocator.Current.GetInstance<AzureGraphService>() 
                     .CreateRoleClaimsAsync(ctx.AuthenticationTicket.Identity);
                  #endregion
              
                  try { 
                     // Sync user and the roles to EPiServer in the background 
                     await ServiceLocator.Current.GetInstance<SynchronizingUserService>()
                        .SynchronizeAsync(ctx.AuthenticationTicket.Identity); 
                  } catch (Exception ex) { 
                  throw new Exception("Name Claim Type: " 
                     + ctx.AuthenticationTicket.Identity.NameClaimType, ex); 
                  } 
               } 
            }
         }); 
        
         // Add stage marker to make sure WsFederation runs on Authenticate (before URL Authorization and virtual roles) 
         app.UseStageMarker(PipelineStage.Authenticate); 

         // Remap logout to a federated logout 
         app.Map(LogoutUrl, map => { 
            map.Run(ctx => { 
               ctx.Authentication.SignOut(); 
               return Task.FromResult(0); 
            }); 
         }); 

         // Tell antiforgery to use the name claim 
         AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Name; 
      } 
   }
}

4. Lastly, add the AzureGraphService class

[ServiceConfiguration(typeof(AzureGraphService))] 
public class AzureGraphService { 
   public async Task CreateRoleClaimsAsync(ClaimsIdentity identity) { 
      // Get the Windows Azure Active Directory tenantId 
      var tenantId = identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
 
      // Get the userId var currentUserObjectId = identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
      var servicePointUri = new Uri(ConfigurationManager.AppSettings["GraphUrl"]); 
      var serviceRoot = new Uri(servicePointUri, tenantId); 
      var activeDirectoryClient = new ActiveDirectoryClient(serviceRoot, 
         async () => await AcquireTokenAsyncForApplication());
      var userResult = await activeDirectoryClient.Users
         .Where(u => u.ObjectId == currentUserObjectId).ExecuteAsync(); 
      var currentUser = userResult.CurrentPage.FirstOrDefault() as IUserFetcher;
      var pagedCollection = await currentUser.MemberOf.OfType<Group>().ExecuteAsync(); 

      do { 
         var groups = pagedCollection.CurrentPage.ToList(); 
         foreach (var role in groups) 
            identity.AddClaim(new Claim(ClaimTypes.Role, role.DisplayName, 
               ClaimValueTypes.String, "AzureGraphService")); 
            pagedCollection = pagedCollection.GetNextPageAsync().Result; 
      } while (pagedCollection != null && pagedCollection.MorePagesAvailable);
 
      identity.AddClaim(new Claim(ClaimTypes.Name, 
         identity.FindFirst("http://schemas.microsoft.com/identity/claims/displayname").Value, 
            ClaimValueTypes.String, "AzureGraphService")); 
   }

   public async Task<string> AcquireTokenAsyncForApplication() { 
      var authenticationContext = new AuthenticationContext(
         string.Format("https://login.windows.net/{0}", 
         ConfigurationManager.AppSettings["TenantName"]), false); 

      // Config for OAuth client credentials  
      var clientCred = new ClientCredential(
         ConfigurationManager.AppSettings["ClientId"], 
         ConfigurationManager.AppSettings["ClientSecret"]); 
      var authenticationResult = authenticationContext.AcquireToken(
         ConfigurationManager.AppSettings["GraphUrl"], clientCred); 

      return authenticationResult.AccessToken; 
   } 
}

You are now ready to run your Episerver site that trusts Azure Active Directory authentication. 🙂

If you went through the trouble of setting this up and got stuck somewhere, feel free to comment below!

%d bloggers like this: