WIF: Active authentication against "usernamemixed" ADFS endpoint

Scenario
One of my customers recently wanted to help write code/ configuration in the following scenario.

ASP.NET web application and WCF service would be hosted on two different machines (IIS web server). User would access web application. And, it would make an "active authentication" call to "usernamemixed" ADFS endpoint. Once it has the token from ADFS, it would be allowed to make the backend WCF service (without any prompt for username/ password) call.

 

Solution

For solution to the above challenge, the following are the steps to be followed (one-by-one).

 

  1. Launch VS 2015 as an administrator
  2. Create a new project with "WCF service application" template
  3. Let it be named "ActiveScenarioService2015"
  4. Modify "Iservice1.cs" to contain the following methods:

 

       
        using System.ServiceModel;

      namespace ActiveScenarioService2015
     {
           [ServiceContract]
           public interface IService1
          {
               [OperationContract]
             string GetData(int value);

              [OperationContract]
             string GetClaim();
          }
       }
  1. Modify "Service1.svc.cs" to contain the following code:

 

      
        using System.Security.Claims;
       using System.ServiceModel;
      using System.Text;

      namespace ActiveScenarioService2015
     {
           public class Service1 : IService1
           {
               public string GetData(int value)
                {
                   return string.Format("You entered: {0}", value);
                }

               public string GetClaim()
                {
                   ClaimsPrincipal claimsPrincipal = OperationContext.Current.ClaimsPrincipal;

                 StringBuilder builder = new StringBuilder();

                    builder.AppendLine(string.Format("User identity: {0}", claimsPrincipal.Identity.Name));
                 foreach (var claim in claimsPrincipal.Claims)
                   {
                       builder.AppendLine(string.Format("Claim Type: {0} Claim Value: {1}", claim.Type, claim.Value));
                 }

                   return builder.ToString();
              }
           }
       }
  1. Ensure you can build the project fine
  2. Add reference to assemblies such as "System.IdentityModel" and "System.IdentityModel.Services"
  3. Manage Nuget packages -> find "System.IdentityModel.Tokens.ValidatingIssuerNameRegistry" -> "Install"
  4. Modify the "web.config" to contain the following configuration items:
   
    <configuration>
     <configSections>
        <section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
   </configSections>
      <appSettings>
       <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
       <add key="ida:FederationMetadataLocation" value="https://adfs.contoso.com/federationmetadata/2007-06/federationmetadata.xml" />
       <add key="ida:ProviderSelection" value="productionSTS" />
    </appSettings>
    <system.web>
        <compilation debug="true" targetFramework="4.6.1" />
      <httpRuntime targetFramework="4.6.1"/>
      </system.web>
     <system.serviceModel>
       <behaviors>
         <serviceBehaviors>
          <behavior>
              <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
           <serviceDebug includeExceptionDetailInFaults="true"/>
             <serviceCredentials useIdentityConfiguration="true">
                <serviceCertificate findValue="CN=localhost" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectDistinguishedName"/>
           </serviceCredentials>
            </behavior>
         </serviceBehaviors>
     </behaviors>
      <protocolMapping>
         <add binding="ws2007FederationHttpBinding" scheme="https" />
      </protocolMapping>
     <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
        <bindings>
          <ws2007FederationHttpBinding>
           <binding>
           <security mode="TransportWithMessageCredential">
                <message>
               <issuerMetadata address="https://adfs.contoso.com/adfs/services/trust/mex" />
               </message>
              </security>
         </binding>
          </ws2007FederationHttpBinding>
      </bindings>
      </system.serviceModel>
    <system.webServer>
      <modules runAllManagedModulesForAllRequests="true"/>
      
     <directoryBrowse enabled="true"/>
   </system.webServer>
   <system.identityModel>
        <identityConfiguration>
         <audienceUris>
              <add value="https://win7.contoso.com/ActiveScenarioService2015/Service1.svc" />
         </audienceUris>
           <issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
              <authority name="adfs.contoso.com/adfs/services/trust">
              <keys>
                  <add thumbprint="BDF5186BFF80A2BC188508F4EEE0E5737CBCD30E" />
              </keys>
               <validIssuers>
                  <add name="adfs.contoso.com/adfs/services/trust" />
              </validIssuers>
             </authority>
            </issuerNameRegistry>
         
           <certificateValidation certificateValidationMode="None" />
        </identityConfiguration>
    </system.identityModel>
  </configuration>
  1. Please note about two applications over here

    1. "CN=localhost"
      • you can launch your certificate store (run -> mmc -> add/ remove snap in/ certificates/ Add .. Select "Computer Account"/ OK
      • you would see a certificate named "localhost" in your "personal" store
      • if not, create a self-signed certificate with "localhost" name form IIS manager
    1. "BDF5186BFF80A2BC188508F4EEE0E5737CBCD30E"
      • it would be the thumbprint value of your "ADFS signing" certificate

 

 

  1. Host the WCF service on IIS
  2. One simple method is just to create virtual directory via VS project properties

  1. Go to IIS Manager/ Create an application pool for "v4.0" .NET framework named as "ActiveScenarioService2015" (for example)
  2. Ensure "Load User Profile" set to "true" for the application pool
  3. Map the IIS hosted application "ActiveScenarioService2015" to the newly created application pool "ActiveScenarioService2015"
  4. Ensure you can browse your service application fine (for example - https://win7.contoso.com/ActiveScenarioService2015/Service1.svc)

 

  1. Next step is to provision the service application on ADFS (in this case - it was 3.0)
  2. On the IIS server "win7.conrtoso.com", Right click the "localhost" certificate in store, "All Tasks" -> "Export" .. Without private key -> to a DER encoded binary X.509 certificate (.CER) file
  3. Go to your ADFS server, and bring in the ".CER" for being used as "encryption" certificate
  4. Ensure ADFS "usernamemixed" endpoint is enabled
  5. Add "Relying Party Trust"

 

 

 

 

Browse the exported "localhost" certificate (.cer) on ADFS server

 

Don't select any - because we are using ADFS "usernamemixed" endpoint for user authentication

 

Provide "Relying Party" identifier to be the service URL

 

 

Finish

 

Add Rule

 

OK

 

 

  1. Now, "RP Trust" provisioning is over in ADFS

 

  1. Next, it is turn of the client side to be ready

 

  1. Create an MVC web application project named "ActiveScenarioWebClient" in VS 2015
  2. Ensure you can build the web application project fine
  3. "Add Service Reference" to the WCF service URL https://win7.contoso.com/ActiveScenarioService2015/Service1.svc
  4. Build the project
  5. Now, turn to "web.confg" and ensure following sections appended or appeared in <system.serviceModel .. /> section

 

   <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_IService1" />
      </basicHttpBinding>
      <wsHttpBinding>
        <binding name="https://adfs.contoso.com/adfs/services/trust/2005/certificatemixed">
          <security mode="TransportWithMessageCredential">
            <transport clientCredentialType="None" />
            <message clientCredentialType="Certificate" establishSecurityContext="false" />
          </security>
        </binding>
        <binding name="https://adfs.contoso.com/adfs/services/trust/2005/usernamemixed">
          <security mode="TransportWithMessageCredential">
            <transport clientCredentialType="None" />
            <message clientCredentialType="UserName" establishSecurityContext="false" />
          </security>
        </binding>
        <binding name="https://adfs.contoso.com/adfs/services/trust/13/windowstransport">
          <security mode="Transport" />
        </binding>
      </wsHttpBinding>
      <ws2007HttpBinding>
        <binding name="https://adfs.contoso.com/adfs/services/trust/13/certificatemixed">
          <security mode="TransportWithMessageCredential">
            <transport clientCredentialType="None" />
            <message clientCredentialType="Certificate" establishSecurityContext="false" />
          </security>
        </binding>
        <binding name="https://adfs.contoso.com/adfs/services/trust/13/usernamemixed">
          <security mode="TransportWithMessageCredential">
            <transport clientCredentialType="None" />
            <message clientCredentialType="UserName" establishSecurityContext="false" />
          </security>
        </binding>
      </ws2007HttpBinding>
      <ws2007FederationHttpBinding>
        <binding name="WS2007FederationHttpBinding_IService1_username">
          <security mode="TransportWithMessageCredential">
            <message>
              <issuer address="https://adfs.contoso.com/adfs/services/trust/2005/usernamemixed"
                binding="wsHttpBinding" bindingConfiguration="https://adfs.contoso.com/adfs/services/trust/2005/usernamemixed">
              </issuer>
              <issuerMetadata address="https://adfs.contoso.com/adfs/services/trust/mex" />
              <tokenRequestParameters>
                <trust:SecondaryParameters xmlns:trust="docs.oasis-open.org/ws-sx/ws-trust/200512">
                  <trust:KeyType xmlns:trust="docs.oasis-open.org/ws-sx/ws-trust/200512">docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey</trust:KeyType>
                  <trust:KeySize xmlns:trust="docs.oasis-open.org/ws-sx/ws-trust/200512">256</trust:KeySize>
                  <trust:KeyWrapAlgorithm xmlns:trust="docs.oasis-open.org/ws-sx/ws-trust/200512">www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p</trust:KeyWrapAlgorithm>
                  <trust:EncryptWith xmlns:trust="docs.oasis-open.org/ws-sx/ws-trust/200512">www.w3.org/2001/04/xmlenc#aes256-cbc</trust:EncryptWith>
                  <trust:SignWith xmlns:trust="docs.oasis-open.org/ws-sx/ws-trust/200512">www.w3.org/2000/09/xmldsig#hmac-sha1</trust:SignWith>
                  <trust:CanonicalizationAlgorithm xmlns:trust="docs.oasis-open.org/ws-sx/ws-trust/200512">www.w3.org/2001/10/xml-exc-c14n#www.w3.org/2001/04/xmlenc#aes256-cbc</trust:EncryptionAlgorithm>
                </trust:SecondaryParameters>
              </tokenRequestParameters>
            </message>
          </security>
        </binding>
      </ws2007FederationHttpBinding>
    </bindings>
    <client>
      <endpoint address="win7.contoso.com/ActiveScenarioService2015/Service1.svc"
        binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1"
        contract="ServiceReference1.IService1" name="BasicHttpBinding_IService1" />
      <endpoint address="https://win7.contoso.com/ActiveScenarioService2015/Service1.svc"
        binding="customBinding" bindingConfiguration="WS2007FederationHttpBinding_IService1"
        contract="ServiceReference1.IService1" name="WS2007FederationHttpBinding_IService1" />
      <endpoint address="https://win7.contoso.com/ActiveScenarioService2015/Service1.svc"
        binding="ws2007FederationHttpBinding" bindingConfiguration="WS2007FederationHttpBinding_IService1_username"
        contract="ServiceReference1.IService1" name="WS2007FederationHttpBinding_IService1_username" />
    </client>
  </system.serviceModel>

 

 

    1. Create a controller named "TestController"
   using ActiveScenarioWebClient.ServiceReference1;
    using System;
   using System.Collections.Generic;
   using System.Linq;
  using System.Web;
   using System.Web.Mvc;

   namespace ActiveScenarioWebClient.Controllers
   {
       public class TestController : Controller
        {
           // GET: Test
            public ActionResult Index()
         {
               var proxy = new Service1Client("WS2007FederationHttpBinding_IService1_username");
               proxy.ClientCredentials.UserName.UserName = @"contoso\administrator";
               proxy.ClientCredentials.UserName.Password = "Password01!";
              var response = proxy.GetClaim();
             ViewBag.Response = response;
                proxy.Close();

              return View();
          }
       }
   }
    1. Add view "Index.cshtml"
     @{
          ViewBag.Title = "Index";
          }
 
          <h2>Index</h2>
          <p>@ViewBag.Response</p>

 

  1. Host the web application on IIS
  2. Access your web application over browser (e.g. https://aadconnect.contoso.com/ActiveScenarioWebClient/test)
  3. The output would be something like

  1. Once we have this much output, we are good with the authentication flow

 

Note:

The sample application tried above is up for your reference is available here. Please launch the VS solution as an administrator to view the sample application.

 

 

I hope this helps!