Using a local ADFS with Exchange 2013

Be sure to read the updates for CU1 (which will run with E15 “baseline”, too): Adjustments for E15 CU1

Some weeks (or month) ago we set up some Exchange 2013 (E15) Servers. Four of them are frontend servers with (among other services) OWA and ECP running.
Since we have lots of non-domain computers here at the university, we try to use ADFS for authentication where possible (we had a running E14 setup as well).

While using ADFS with V14 wasn’t that easy, setting up E15 to use ADFS (V2.0) worked like a charm – nearly.
In my description below, i will only write about OWA, since ECP will be very similar.


  • A working installation of E15, configured for Windows-Integrated-Authentication with OWA
  • A working setup of ADFS (at least V2.0) running somewhere in your domain
  • The OWA Appliction-Pool-Accounts should have the right to impersonate users (in our setup OWA runs as SYSTEM)


  • Fiddler will be useful to debug
  • WinMerge was handy to synchronize the Web.config files
  • Visual-Studio (C#/VB Express will do)

1. Setting up the RelyingParty
Since Microsoft doesn’t support FedUtil anymore (or doesn’t support it for WIF on .NET 4.5) we will have to create the RelyingParty manually. Important are the following settings:

  1. ADFS 2.0 Profile
  2. Passive Federation Endpoint pointing to:

We will only submit two claims: UPN and Name.
Name will be used for entries into the IIS-Log, so use Windows-Account-Name or UPN for that.
UPN will be used to impersonate the user and only the UserPrincipalName from the active directory will be acceptable here.
Create a custom rule with the code below or use one of the templates to create that.

c:[Type == "", Issuer == "AD AUTHORITY"]
=> issue(
store = "Active Directory",
types = ("", ""),
query = ";userPrincipalName,userPrincipalName;{0}",
param = c.Value);

2. Configuring <System.IdentityModel>

Straight forward here. Change this xml for your environment and add it to the location-Tag inside the Web.config.

      <remove type="System.IdentityModel.Tokens.SamlSecurityTokenHandler, System.IdentityModel, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      <remove type="System.IdentityModel.Tokens.Saml2SecurityTokenHandler, System.IdentityModel, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      <add type="System.IdentityModel.Tokens.SamlSecurityTokenHandler, System.IdentityModel, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089">
        <samlSecurityTokenRequirement mapToWindows="true" />
      <add type="System.IdentityModel.Tokens.Saml2SecurityTokenHandler, System.IdentityModel, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089">
        <samlSecurityTokenRequirement mapToWindows="true" />
      <remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      <add type="System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler, System.IdentityModel.Services, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      <add value="" />
    <issuerNameRegistry type="System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089">
    <cookieHandler requireSsl="true" />
    <wsFederation passiveRedirectEnabled="true" issuer="" realm="" requireHttps="true" />

3. Modifying other parts of the Web.Config
Now we have to get IIS to load System.IdentityModel and use it as authentication.
I will omit lot’s of code, so you probably have to look a little bit, where to insert it.

    <section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
    <section name="" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=, Culture=neutral, PublicKeyToken=B77A5C561934E089" />

  <location inheritInChildApplications="false">
        <!-- [ADFS] -->
	<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="" />
	<add name="WSFederationAuthenticationModule" type="ZDV.IdentityModel.Services.WSFederationAuthenticationModule, ZDV.IdentityModel.Services, Version=, Culture=neutral, PublicKeyToken=publicToken" preCondition="" /> <!-- See below -->
          <!-- The default config would be the following, but this will lead to errors, if a user tries to append files with the HTML 4.1 file upload -->
          <add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="" />
        <!-- [/ADFS] --> 
        <remove name="ServiceModel" />
        <remove name="ServiceModel-4.0" />
        <remove name="Session" />
        <remove name="Profile" />
        <add name="HttpProxy" type="Microsoft.Exchange.HttpProxy.ProxyModule,Microsoft.Exchange.FrontEndHttpProxy,Version=,Culture=neutral,PublicKeyToken=31bf3856ad364e35" preCondition="" />
        <add name="cafe_exppw" />
      <!-- .. something here .. -->
      <machineKey decryptionKey="ABC" validationKey="ABC" /> <!-- Set the machine key farm wide, or else ADFS will not be able to use a non-sticky load-balancer environment -->
      <!-- .. something here .. -->
      <httpRuntime maxUrlLength="500" maxRequestLength="35000" requestValidationMode="4.5" /><!-- [ADFS]The requestValidationMode has to be WIF compatible[/ADFS] -->
      <!-- .. something here .. -->

    <!-- .. something here .. -->
    <!-- [ADFS] -->
    <add key="ida:FederationMetadataLocation" value="" />
    <add key="ida:Issuer" value="" />
    <add key="ida:ProviderSelection" value="productionSTS" />
    <!-- [/ADFS] -->

    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
        <assemblyIdentity name="ZDV.IdentityModel.Services" publicKeyToken="publicKeyToken" culture="neutral" />
        <codeBase version="" href="file:///C:\Program Files\Microsoft\Exchange Server\V15\FrontEnd\HttpProxy\bin\ZDV.IdentityModel.Services.dll" />
      <!-- .. something here .. -->

As mentioned the upload by HTML 4.1 can make problems. It seems that this comes from loading the request inside the WSFederation-Authentication Module.
To bypass this, we overwrote the default module with our own:

using System;
using System.Linq;
using System.Threading;
using System.Web;

namespace ZDV.IdentityModel.Services
    public class WSFederationAuthenticationModule : System.IdentityModel.Services.WSFederationAuthenticationModule
        protected override void OnAuthenticateRequest(object sender, EventArgs args)
            bool skip = Thread.CurrentPrincipal != null;
            //skip = skip && Thread.CurrentPrincipal.Identity.IsAuthenticated;
            skip = skip && HttpContext.Current != null &&
                            .Any(key => String.Equals(key, "wa", StringComparison.OrdinalIgnoreCase));
            skip = skip && (
                HttpContext.Current.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase) &&
                           .Any(key => String.Equals(key, "wa", StringComparison.OrdinalIgnoreCase)));

            if(skip) return;

            base.OnAuthenticateRequest(sender, args);

Build it and deploy it to the Exchange/V15/HttpProxy/bin Directory.

4. Redirections

Since E15 doesn’t know about ADFS now, the logout has to be redirected.
URL Rewrite (from will do the job for us with the following configuration:

<?xml version="1.0" encoding="UTF-8"?>
                <clear />
                <rule name="Trailing Slash OWA ECP" stopProcessing="true">
                    <match url="(.*[^/])$" />
                    <conditions logicalGrouping="MatchAny" trackAllCaptures="false">
                        <add input="{URL}" pattern="^/owa$" />
                        <add input="{URL}" pattern="^/ecp$" />
                    <action type="Redirect" url="{R:1}/" />
                <rule name="Redirect Logon Requests" stopProcessing="true">
                    <match url="^owa/auth/logon\.aspx$" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
                    <action type="Redirect" url="" />
                <rule name="Redirect Logout Requests V14" stopProcessing="true">
                    <match url="^owa/auth/logoff\.aspx$" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
                    <action type="Redirect" url="" />
                <rule name="Redirect Logout Requests V15" stopProcessing="true">
                    <match url="^owa/logoff\.owa$" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
                    <action type="Redirect" url="" />