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 AD

The following steps are the changes you need to make on the Azure portal.

  • Portal > Active Directory > App registrations > + New application registration

 

 

 

  • Fill up the details of your app. The sign-on URL can be changed later so you can enter a local site URL for now

 

 

 

 

 

  • Once created, click the app from the list and add a security key:
    • App > All Settings > Keys > + Add new key and save (important)
  • Enter the logout URL (the URL to redirect to after logging out of Azure)
    • App > All Settings > Properties > Logout URL
  • Enter the Reply URLs (Enter the logout URL here, and any other URL that Azure will redirect to)
    • App > All Settings > Reply URLs
  •  Set permissions to the app
    •  App > All Settings > Required permissions:
      • Application Permissions – Read directory data
      • Delegated Permissions – Read directory data
      • Delegated Permissions – Sign in and read user profile
    • Save
    • Go back to the Required permissions window > Click on Grant permissions (important)
  • The following app details will later be needed in your Episerver web.config
    • Metadata Address (Active Directory > App Registrations > Endpoints > Federation Metadata Document)
    • App ID URI (App > All Settings > Properties > App ID URI)
    • Tenant Name (name of active directory)
    • Application ID (App > All Settings > Application ID)
    • Security key (the generated security key in an earlier step above)
    • Windows graph URL (written 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” />
  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!

Add a Comment

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