Hide menu Last updated: Mar 16 2017
Area: Episerver CMS Applies to versions: 10 and higher
Other versions:

Integrate Azure AD using OpenID Connect

This topic explains how to use OpenID Connect to integrate with Azure Active Directory. Here we describe how an Episerver application can use the OpenID Connect to sign-in users from a single/multi-tenant environment, using the ASP.Net OpenID Connect OWIN middleware.

About Azure Active Directory and OpenID

Azure Active Directory (Azure AD) is Microsoft's multi-tenant cloud-based directory and identity management service. Azure AD provides single sign-on (SSO) access to many cloud-based SaaS applications, and includes a full suite of identity management capabilities.

OAuth is an open standard for authorization also used by Azure AD. OpenID Connect is built on top of OAuth and extends this so you can use it as an authentication protocol rather than just an authorization protocol.

For more information about how the protocols works, see Authentication Scenarios for Azure AD and Integrate Azure AD into a web application using OpenID Connect. For role-based access control, see Adding application roles in Azure AD.

Prerequisites

Disable Role and Membership Providers

Disable the built-in Role and Membership providers in web.config.

Leave the profile system enabled since edit and admin views use this system for language settings, it is possible to use another profile system on the website.

<authentication mode="None" />
    <membership>
      <providers>
        <clear/>
      </providers>
    </membership>
    <roleManager enabled="false">
      <providers>
        <clear/>
      </providers>
    </roleManager>

Configure Episerver to support claims

Enable claims on virtual roles by setting the addClaims property. Also, add the provider SynchronizingRolesSecurityEntityProvider for security entities, which is used by the set access rights dialog box and impersonating users. Users and groups are synchronized to custom Episerver tables in the database when a user is authenticated (see ISynchronizingUserService in the code example below).

<episerver.framework>
    <securityEntity>
      <providers>
        <add name="SynchronizingProvider" type ="EPiServer.Security.SynchronizingRolesSecurityEntityProvider, EPiServer"/>
      </providers>
    </securityEntity>
    <virtualRoles addClaims="true">
       //existing virtual roles
    </virtualRoles>

It is also possible to replace virtual roles with roles defined in the manifest to delegate this control from the application to Azure, see Adding application roles in Azure Active Directory.

Install NuGet packages

Open Package Manager in Visual Studio and install the following packages:

Install-Package Microsoft.Owin.Security.Cookies
Install-Package Microsoft.Owin.Security.OpenIdConnect
Install-Package Microsoft.Owin.Host.SystemWeb
Update-Package Microsoft.IdentityModel.Protocol.Extensions -Safe
 

Note: Always use Microsoft.IdentityModel.Protocol.Extensions package version 1.0.2 or later. Previous versions contain a critical bug that might cause threads to hang.

Configure OpenID Connect

To configure the OpenID Connect, add the following code in the startup class for OWIN middleware. The SecurityTokenValidated-event is used to synchronize the user and group membership to Episerver. You can also use this event for custom logic (for example, adding custom data to the user profile).

using System;
using System.Configuration;
using System.Globalization;
using System.Threading.Tasks;
using System.Web;

using Owin;
using Microsoft.Owin;
using Microsoft.Owin.Extensions;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Notifications;
using Microsoft.Owin.Security.OpenIdConnect;

using EPiServer.Security;
using EPiServer.ServiceLocation;

public class Startup
{
  // <add key="ida:AADInstance" value="https://login.microsoftonline.com/{0}" />
  private static readonly string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
 
  // <add key="ida:ClientId" value="Client ID from Azure AD application" />
  private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];

  //<add key="ida:PostLogoutRedirectUri" value="https://the logout post uri/" />
  private static readonly string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];

  private static string commonAuthority = String.Format(CultureInfo.InvariantCulture, aadInstance, "common/");

  const string LogoutPath = "logout path"; 

  public void Configuration(IAppBuilder app)
  {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
        app.UseCookieAuthentication(new CookieAuthenticationOptions());
        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            ClientId = clientId,
            Authority = commonAuthority,
            PostLogoutRedirectUri = postLogoutRedirectUri,
            TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
             {
                 ValidateIssuer = false,
                 RoleClaimType = ClaimTypes.Role
             },
             Notifications = new OpenIdConnectAuthenticationNotifications
             {
                 AuthenticationFailed = context =>
                 {
                     context.HandleResponse();
                     context.Response.Write(context.Exception.Message);
                     return Task.FromResult(0);
                 },
                 RedirectToIdentityProvider = context =>
                 {
                     // Here you can change the return uri based on multisite
                     HandleMultiSitereturnUrl(context);
                     
                     // To avoid a redirect loop to the federation server send 403 
                     // when user is authenticated but does not have access
                     if (context.OwinContext.Response.StatusCode == 401 && 
                         context.OwinContext.Authentication.User.Identity.IsAuthenticated)
                     {
                         context.OwinContext.Response.StatusCode = 403;
                         context.HandleResponse();
                      }
                      return Task.FromResult(0);
                    },
                    SecurityTokenValidated = (ctx) =>
                    {
                       var redirectUri = new Uri(ctx.AuthenticationTicket.Properties.RedirectUri, UriKind.RelativeOrAbsolute);
                       if (redirectUri.IsAbsoluteUri)
                       {
                            ctx.AuthenticationTicket.Properties.RedirectUri = redirectUri.PathAndQuery;
                        }
                           //Sync user and the roles to EPiServer in the background
                        ServiceLocator.Current.GetInstance<ISynchronizingUserService>().
                        SynchronizeAsync(ctx.AuthenticationTicket.Identity);
                        return Task.FromResult(0);
                     }
                    }
                   });
                app.UseStageMarker(PipelineStage.Authenticate);
                app.Map(LogoutPath, map =>
               {
                  map.Run(ctx =>
                  {
                    ctx.Authentication.SignOut();
                    return Task.FromResult(0);
                  });
               });
           }

        
         private void HandleMultiSitereturnUrl(
                 RedirectToIdentityProviderNotification<Microsoft.IdentityModel.Protocols.OpenIdConnectMessage, 
                                         OpenIdConnectAuthenticationOptions> context)
        {
           // here you change the context.ProtocolMessage.RedirectUri to corresponding siteurl
           // this is a sample of how to change redirecturi in the multi-tenant environment
            if (context.ProtocolMessage.RedirectUri == null)
            {
                var currentUrl = EPiServer.Web.SiteDefinition.Current.SiteUrl;
                context.ProtocolMessage.RedirectUri = new UriBuilder(
                   currentUrl.Scheme, 
                   currentUrl.Host, 
                   currentUrl.Port, 
                   HttpContext.Current.Request.Url.AbsolutePath).ToString();
            }
        }
    }

Adding application roles in Azure Active Directory

By default, you need to declare application roles in the active directory application such as WebEditors and WebAdmins. Either the application owner (developer of the app) or the global administrator of the developer’s directory can declare roles for an application.

  1. In the Azure Management Portal, navigate to the Active Directory node and go to the Applications tab.
  2. Click to open the application for which you wish to declare application roles.
  3. Click the Manage Manifest action button on the bottom bar and select Download Manifest.
  4. Open the manifest file in a JSON editor of your choice.
  5. Locate the appRoles setting and insert the appRole definitions in the array.  
    This is an example of approles that declare WebAdmins and WebEditors. You can modify it according to your application roles. Note that you need to generate new Guid for each role declaration.
     "appRoles": [
        {
          "allowedMemberTypes": [
            "User"
          ],
          "description": "Editor can edit the site.",
          "displayName": "WebEditors",
          "id": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
          "isEnabled": true,
          "value": "WebEditors"
        },
        {
          "allowedMemberTypes": [
            "User"
          ],
          "description": "Admins can manage roles and perform all task actions.",
          "displayName": "WebAdmins",
          "id": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX",
          "isEnabled": true,
          "value": "WebAdmins"
        },
         {
          "allowedMemberTypes": [
            "User"
          ],
          "description": "Admin the site.",
          "displayName": "Admininstrators",
          "id": "XXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX",
          "isEnabled": true,
          "value": "Admininstrators"
        }
      ],
  6. After declaration of the application roles, you need to upload the manifest to Azure Active Directory application. Refer to Role-based access control for more information.

Assigning users and groups to application roles

When a global administrator of the customer’s organization has installed your application, they (or a user accounts administrator) can assign users and groups to your application:

  1. Navigate to the users tab under the application to which you would like to assign users and groups.
  2. Select a user and click on the Assign action on the bottom bar. Here you can assign the desired role to the user.

Known issues

  • If you are using later version of System.IdentityModel.Tokens.Jwt 4.0.0, it is required to set RoleClaimType = ClaimTypes.Role. Otherwise, use RoleClaimType = "roles" (in the TokenValidationParameters).
  • If the application throws an antiforgerytoken exception like “AntiForgeryToken: A Claim of Type NameIdentifier or IdentityProvider Was Not Present on Provided ClaimsIdentity”, set AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier.

Comments