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.
- Azure Active Directory > Users and groups > All groups > + New group
- Enter details and assign the relevant users to this group
- Repeat steps 1&2 for all four Episerver groups mentioned above
Configure your Episerver site to trust Azure’s Identity Provider (Active Directory)
- On your web.config, replace your membershipProvider and roleManager tags:
- <membership><providers><clear /></providers></membership>
- <roleManager enabled=”false”><providers><clear /></providers></roleManager>
- 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” />
- 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!