Using Azure Active Directory as identity provider
EPiServer has now support for federated security and there is a good description of how to get started,http://world.episerver.com/Documentation/Items/Developers-Guide/EPiServer-CMS/75/Security/Federated-Security/
During my presentation at the partner forum, I showed how to use Azure Active Directory to manage authentication for EPiServer, here's the summary.
Azure
Open the Azure portal and create a new application in the Active Directory that you want to use for authentication, select the ADD APPLICATION MY ORGANIZATION IS DEVELOPING, enter a name and select WEB APPLICATION AND/OR WEB API.
In the last section, enter the url to your site in the SIGN-ON URL field and a unique url to identify your application in the APP ID URI field, this is the url that you also should specify in the web.config for your EPiServer site , click the Complete button.
Endpoints
Once the application is created, we enter the configuration tab get the metadata url, click on the icon VIEW ENDPOINTS along the bottom to open dialog, highlight the field FEDERATION DOCUMENT METADATA and copy the value, this should also be pasted into the web.config.
Client ID and Secret Key
Before we close the Azure portal, we need to have another couple of settings from the configuration to get it work.
Copy the value from the field CLIENT ID and paste it to your web.config, we must also create a secret key that is used with the client ID when requesting the Azure's Graph API. Choose if your secret key will be valid for a year or two, and then click save, when the application is saved, you can copy the value of the secret key and paste it to the web.config.
The last thing we need to do before we jump over to EPiServer is to set permissions so that the application can request and read the information via the Graph API, select Windows Azure Active Directory application and at least read access for the Application Permissions as well as Read directory data and Enable sign-on and Read users profiles for the Delegated Permissions, click save.
Update web.config
When all settings from Azure has been copied to your web.config, the following appSettings should be included:
1: <add key="MetadataAddress" value="https://login.windows.net/c570145b-647d-456c-9a3b-171724a4af74/federationmetadata/2007-06/federationmetadata.xml" />
2: <add key="Wtrealm" value="https://kalleljungepiserver.onmicrosoft.com/partnerforum" />
3: <add key="TenantName" value="kalleljungepiserver.onmicrosoft.com" />
4: <add key="ClientId" value="4181422f-7c7d-452a-a84c-91b03e91a062" />
5: <add key="ClientSecret" value="sZvD2A8ac5jv4ifD2GfTGLt3uoPrbfr1teh1nQ4wQjU=" />
6: <add key="GraphUrl" value="https://graph.windows.net" />
Create user groups
Select the GROUPS tab for your directory and click on ADD A GROUP, enter a name and click save, I choose to create a group named WebAdmins since this group has access rights to both EPiServers edit and admin interface as default.
Once the group is created, go in and add the users who will be members.
EPiServer
The federated security require OWIN middleware to run, install the following nuget packages.
Install-Package Microsoft.Owin.Security.Cookies
Install-Package Microsoft.Owin.Security.WsFederation
Install-Package Microsoft.Owin.Host.SystemWeb
Install-Package Microsoft.Azure.ActiveDirectory.GraphClient
Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory
Retrieve groups for the authenticated user
One problem with Azure Active Directory is that the groups that user is a member of is not sent with the Claims, we need the groups to be able to set access rights in EPiServer.
Before EPiServer synchronizes the roles of the authenticated user, they must retrieved through a request to the graph API. I created a AzureGraphService that handles the request to Azure and retrieves roles/grupes for the authenticated user and saves it as Claims.
1: using System;
2: using System.Configuration;
3: using System.Linq;
4: using System.Security.Claims;
5: using System.Threading.Tasks;
6: using EPiServer.ServiceLocation;
7: using Microsoft.Azure.ActiveDirectory.GraphClient;
8: using Microsoft.IdentityModel.Clients.ActiveDirectory;
9:
10: namespace Federated_Security.Business.Security
11: {
12: [ServiceConfiguration(typeof(AzureGraphService))]
13: public class AzureGraphService
14: {
15: public async Task CreateRoleClaimsAsync(ClaimsIdentity identity)
16: {
17: // Get the Windows Azure Active Directory tenantId
18: var tenantId = identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
19:
20: // Get the userId
21: var currentUserObjectId = identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
22:
23: var servicePointUri = new Uri(ConfigurationManager.AppSettings["GraphUrl"]);
24: var serviceRoot = new Uri(servicePointUri, tenantId);
25: var activeDirectoryClient = new ActiveDirectoryClient(serviceRoot,
26: async () => await AcquireTokenAsyncForApplication());
27:
28: var userResult = await activeDirectoryClient.Users
29: .Where(u => u.ObjectId == currentUserObjectId).ExecuteAsync();
30: var currentUser = userResult.CurrentPage.FirstOrDefault() as IUserFetcher;
31:
32: var pagedCollection = await currentUser.MemberOf.OfType<Group>().ExecuteAsync();
33: do
34: {
35: var groups = pagedCollection.CurrentPage.ToList();
36: foreach (Group role in groups)
37: {
38: ((ClaimsIdentity)identity).AddClaim(new Claim(ClaimTypes.Role, role.displayName, ClaimValueTypes.String, "AzureGraphService"));
39:
40: }
41: pagedCollection = pagedCollection.GetNextPageAsync().Result;
42: } while (pagedCollection != null && pagedCollection.MorePagesAvailable);
43: }
44:
45: public async Task<string> AcquireTokenAsyncForApplication()
46: {
47: var authenticationContext = new AuthenticationContext(string.Format("https://login.windows.net/{0}",
48: ConfigurationManager.AppSettings["TenantName"]), false);
49:
50: // Config for OAuth client credentials
51: var clientCred = new ClientCredential(ConfigurationManager.AppSettings["ClientId"], ConfigurationManager.AppSettings["ClientSecret"]);
52: var authenticationResult = authenticationContext.AcquireToken(ConfigurationManager.AppSettings["GraphUrl"], clientCred);
53: return authenticationResult.AccessToken;
54: }
55: }
56: }
Owin configuration
Add a owin startup class and make sure you call the AzureGraphService before EPiServer is synchronizing the roles.
1: using System;
2: using System.Security.Claims;
3: using System.Threading.Tasks;
4: using System.Web.Helpers;
5: using System.Configuration;
6: using Federated_Security.Business.Security;
7: using Owin;
8: using Microsoft.Owin;
9: using Microsoft.Owin.Extensions;
10: using Microsoft.Owin.Security;
11: using Microsoft.Owin.Security.Cookies;
12: using Microsoft.Owin.Security.WsFederation;
13: using EPiServer.Security;
14: using EPiServer.ServiceLocation;
15:
16: [assembly: OwinStartup(typeof(Federated_Security.Startup))]
17:
18: namespace Federated_Security
19: {
20: public class Startup
21: {
22: const string LogoutUrl = "/util/logout.aspx";
23:
24: public void Configuration(IAppBuilder app)
25: {
26: // Enable cookie authentication, used to store the claims between requests
27: app.SetDefaultSignInAsAuthenticationType(WsFederationAuthenticationDefaults.AuthenticationType);
28: app.UseCookieAuthentication(new CookieAuthenticationOptions
29: {
30: AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
31: });
32:
33: // Enable federated authentication
34: app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions()
35: {
36: // Trusted URL to federation server meta data
37: MetadataAddress = ConfigurationManager.AppSettings["MetadataAddress"],
38:
39: // Value of Wtreal must *exactly* match what is configured in the federation server
40: Wtrealm = ConfigurationManager.AppSettings["Wtrealm"],
41:
42: Notifications = new WsFederationAuthenticationNotifications()
43: {
44: RedirectToIdentityProvider = (ctx) =>
45: {
46: // To avoid a redirect loop to the federation server send 403 when user is authenticated but does not have access
47: if (ctx.OwinContext.Response.StatusCode == 401 && ctx.OwinContext.Authentication.User.Identity.IsAuthenticated)
48: {
49: ctx.OwinContext.Response.StatusCode = 403;
50: ctx.HandleResponse();
51: }
52: return Task.FromResult(0);
53: },
54: SecurityTokenValidated = async (ctx) =>
55: {
56: // Ignore scheme/host name in redirect Uri to make sure a redirect to HTTPS does not redirect back to HTTP
57: var redirectUri = new Uri(ctx.AuthenticationTicket.Properties.RedirectUri, UriKind.RelativeOrAbsolute);
58: if (redirectUri.IsAbsoluteUri)
59: {
60: ctx.AuthenticationTicket.Properties.RedirectUri = redirectUri.PathAndQuery;
61: }
62:
63: #region Azure
64:
65: // Create claims for roles
66: await ServiceLocator.Current.GetInstance<AzureGraphService>().CreateRoleClaimsAsync(ctx.AuthenticationTicket.Identity);
67:
68: #endregion
69:
70: // Sync user and the roles to EPiServer in the background
71: await ServiceLocator.Current.GetInstance<SynchronizingUserService>().SynchronizeAsync(ctx.AuthenticationTicket.Identity);
72: }
73: }
74: });
75:
76: // Add stage marker to make sure WsFederation runs on Authenticate (before URL Authorization and virtual roles)
77: app.UseStageMarker(PipelineStage.Authenticate);
78:
79: // Remap logout to a federated logout
80: app.Map(LogoutUrl, map =>
81: {
82: map.Run(ctx =>
83: {
84: ctx.Authentication.SignOut();
85: return Task.FromResult(0);
86: });
87: });
88:
89: // Tell antiforgery to use the name claim
90: AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Name;
91: }
92: }
93: }
Ready
Start up your website and try to access the EPiServer editor interface, now you should be redirected to the Azure login page.
How did you retrieve the tenantname? I have seemed to missed this part and now recieve a AdalServiceexception No service namespace named '...'
So this is a bit old now, and I guess some things have changed. I've set this up on my dev machine and after successfully authenticating in Azure and being sent back to EPiServer I get this error:
IDX10213: SecurityTokens must be signed. SecurityToken: '{0}'.
@Olav
I got the exact same error after signing in with azure AD. After alot of testing and debugging I finally tested downgrading the Microsoft.IdentityModel.Protocol.Extensions package from version 1.0.3.308261200 to 1.0.2.206221351 and it just worked after that.