Hide menu Last updated: Sep 26 2014

Federated security [BETA]

Introduction

Federated security makes it posible to separate the service a client is accessing from the associated authentication and authorization procedures, for instance to enable collaboration across multiple systems, networks, and organizations.

This document describes how to configure federated security where EPiServer acts as a claims aware application. Federated security includes features such as Single-Sign-On (SSO) that allows a single user authentication process across multiple IT systems or even organizations. The protocol used is WS-Federation which is a specification supported by a wide range of federation software, for example Active Directory Federation Services (ADFS). Other federation specifications may require 3rd party software. In documentation the Relying party, or RP, refers to the web site running EPiServer. The identity server/identity provider/federation server/Security Token Service(STS) is a 3rd party software such as ADFS.

In short, when autentication is required (EPiServer sends HTTP status code 401) a redirect sends the user to an identity provider that after authentication sends back claims about the user. Role claims can be used to assign access rights to content or enable access to edit mode

To be able to support assigning access rights to role claims or sending notifications to a user that is not logged in all users and roles are synchronized to the database. The synchronization of users and roles is triggered when an identity provider sends back claims about a user to EPiServer.

This document does not cover basics in federated security or how to configure ADFS.

Limitations

  • The workflow functionality in CMS, that is based on Workflow Foundation 3.5, requires membership and role providers and is not compatible with federated security.
  • There is no user interface to manage roles and users synchronized from the identity provider, everything that is synchronized will be visible for setting access rights by an editor.
  • Not yet supported by Commerce

Windows Identity Foundation and OWIN

Federated security was originally implemented in Windows Identity Foundation (WIF), but is as of .NET 4.5 fully integrated into the core framework. Microsoft has since the release of .NET 4.5 developed a new module called Microsoft.Owin.Security.WSFederation which simplifies configuration but builds on the same proven platform support. This module requires the OWIN framework which contains abstractions to simplify building .NET middleware that need to run on multiple hosts. See more details about OWIN and EPiServer.

Step by step

To be able to complete this step-by-step you need:

  • Minimum required versions EPiServer.CMS.Core 7.14 and EPiServer.CMS.UI 7.14
  • The site must be able to access the identity server meta data URL
  • The site must be registered as a Relying Party Trust in the identity server, see the federation provider documentation for details
  • The identity provider must at least send a Name claim for each user but should also send Role claims to enable setting access righs in EPiServer

1. Disable Role and Membership Providers

The built-in Role and Membership providers does not support federated security so first step is disabling them in web.config:

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

2. Configure EPiServer to support federation

Make sure claims is enabled on virtual roles by setting the addClaims-property. Also add the provider for security entities, this provider system is used by for example the set access rights dialog and impersonating users. The SynchronizingRolesSecurityEntityProvider configured in the example below is using the SynchronizingUserService that is used in step 4.

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

3. Install NuGet packages

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

Install-Package Microsoft.Owin.Security.Cookies
Install-Package Microsoft.Owin.Security.WsFederation
Install-Package Microsoft.Owin.Host.SystemWeb

*Please upgrade the Microsoft.IdentityModel.Protocol.Extensions to >= 1.0.2.

4. Configure WSFederation

The WSFederation package is configured from code rather from configuration files which is a practice used for Owin based middleware, see ASP.NET documentation for details on having different configuration in production and development.

Add the following code to the template project. The MetadataAdress should be replaced with URL to the federation server and the Wtrealm must match exacly what is registrered in the federation server. See inline comments for more details.

using EPiServer.Framework;
using EPiServer.Security;
using EPiServer.ServiceLocation;
using Microsoft.Owin;
using Microsoft.Owin.Extensions;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.WsFederation;
using Owin;
using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web.Helpers;
using WsFederationSite;

[assembly: OwinStartup(typeof(Startup))]

namespace WsFederationSite
{
    public class Startup
    {
        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 = "https://devdc.dev.test/federationmetadata/2007-06/federationmetadata.xml",
                //Value of Wtreal must *exactly* match what is configured in the federation server
                Wtrealm = "https://localhost:44303/",
                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 = (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;
                            }
                            //Sync user and the roles to EPiServer in the background
                            ServiceLocator.Current.GetInstance<SynchronizingUserService>().SynchronizeAsync(ctx.AuthenticationTicket.Identity);
                            return Task.FromResult(0);
                        }
                }
            });
            //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;
        }
    }
}

Troubleshooting

Generic error when redirected to identity provider

Identity providers, such as ADFS, will not display helpful error information on the login screen since that could compromise security. Check the event log of the identity provider to get more details about the cause of an error.

The most common cause is that WtRealm in the code above does not exactly match the value entered as "Relying party identifiers" in the identity provider. Be aware that the value of the WtRealm is just an identifier and does not have to be the URL to the site. The URL that the identity provider posts back the claims to is in most cases another setting defined in the identity provider.

SSL related issues - The remote certificate is invalid according to the validation procedure

Both servers should be using SSL to the protect the communication, for dev/test purposes it is possible to use self-signed certificates on both the web server and identity server. The web server must trust the certificate of the SSL connection to the identity server to be able to access the meta data document defined in the code, when using self-signed certificates you can add the root certificate to the web server.

Value cannot be null. Parameter name: userName

The identity provider may not have not been configured to send a Name-claim, refer to the identity provider documentation to resolve this.

Login and logout from the Alloy templates

Alloy templates use Forms Authentication in the Login/Logout link at the bottom the layout, to provide federated authentication change the Login link to send a "401 Access Denied" which will trigger the WS Federation middleware, for Logout use either "/Util/Logout.aspx" which is remapped in the example above or call the same Signout-method in the templates.

401.2 Access Denied when accessing edit mode instead of redirect to identity provider

If the project previously did not have an Owin startup class and optimizeCompilations was enabled then sometimes the new code is never executed, the result is that you get a 401.2 Access Denied instead of the redirect to the identity provider. Temporarily set optimizeCompilations in web.config to false to clear the cache.

401.2 Access Denied or 403 Forbidden when accessing edit mode after redirect to identity provider

When the identity provider does not send the required role claims you will not get access to edit mode. You could for example configure the identity provider to send the CmsAdmins role claim for a specific group of users.

Enable logging for Owin Security

It is possible to view warning messages reported by Owin Security by adding this configuration to web.config. These warning messages could contain information about errors in the WS-Federation communication protocol.

 <system.diagnostics>
    <switches>
      <add name="Microsoft.Owin.Security" value="Warning" />
    </switches>
    <trace autoflush="true"></trace>
    <sharedListeners>
      <add name="file" type="System.Diagnostics.TextWriterTraceListener" initializeData="App_Data\OwinSecurity.log" />
    </sharedListeners>
    <sources>
      <source name="Microsoft.Owin.Security">
        <listeners>
          <add name="file" />
        </listeners>
      </source>
    </sources>
  </system.diagnostics>

Alloy: The login link redirects to "/Login.aspx"

The Alloy templates assumes you are running forms authentication, you can either remove the login link if not required or implement custom login functionality. For example create a controller or web forms page that sends a 401 Access Denied for non-authenticated users.

Comments