The way security is often still handled these days with each application keeping track of their own users is somewhat dated. Some form of Federated security, where a single separate server is responsible for the security of a whole series of applications, is the way to go. On the internet there are plenty of examples of this with applications using things like OAuth and leaving their security to others. In windows the preferred form of federated security is through Windows Identity Foundation and it is real easy to secure an ASP.NET site or WCF service using Windows Identity Foundation.

 

How about securing a workflow service?

As a workflow 4 service is just another WCF service securing it is just as easy as the steps below will demonstrate.

 

Before we start we need to install Windows Identity Foundation and the related WIF SDK using the two links provided.

To start with I created a small workflow service and a simple client application. No security yet and the client works as expected.

The console app is real simple and looks like this:

static void Main(string[] args)
{
    try
    {
        var proxy = new ServiceClient();
 
        var data = proxy.GetData(42);
        Console.WriteLine(data);
    }
    catch (Exception ex)
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine(ex.Message);
        Console.ResetColor();
    }
 
    Console.ReadLine();
}

The service web.config is also standard and pretty short:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <bindings />
    <client />
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
</configuration>

This just works as expected and when I run I see the following output:

 

Securing this service using Windows Identity Foundation

First thing we need when we want to use WIF is a Security Token Service which will handle the actual security checks and provide the application with the required claims. There is an add-on for Active Directory on Windows 2008 we could use but in this case I am going to use the StarterSTS Dominick Baier wrote. Instead of hosting this myself I am going to use a publicly available instance hosted here by Thinktecture.

Adding the STS to the service is quite easy. With the WIF SDK another VS2010 menu option and wizard was installed to guide us through the process. One important thing is that VS2010 must run as administrator else this wizard will fail to complete.

The first page wants to know about the config file and the service URL used.

In the next page select existing STS and enter the address where the STS federation metadata is located, in the case of the Thinktecture StarterSTS this is https://identity.thinktecture.com/stsdm/FederationMetadata/2007-06/FederationMetadata.xml

Next specify to use encryption, using a default generated certificate will do just fine now.

And that is all we need, click through and select finish. If Visual Studio isn’t run in admin mode this will result in an error, if this happens just restart VS2010 with admin privileges and do it again.

After this wizard has completed the web.config file will contain quite a bit more data and look something like this:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  </configSections>
  <location path="FederationMetadata">
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
  </location>
  <system.web>
    <compilation debug="true" targetFramework="4.0">
      <assemblies>
        <add assembly="Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
      </assemblies>
    </compilation>
  </system.web>
  <system.serviceModel>
    <bindings>
      <ws2007FederationHttpBinding>
        <binding>
          <security mode="Message">
            <message>
              <issuerMetadata address="https://identity.thinktecture.com/stsdm/users/issue.svc/mex" />
              <claimTypeRequirements>
                <!--Following are the claims offered by STS 'http://sample.thinktecture.com/trust/stsdm'. Add or uncomment claims that you require by your application and then update the federation metadata of this application.-->
                <add claimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" isOptional="true" />
                <add claimType="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" isOptional="true" />
                <!--<add claimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" isOptional="true" />-->
                <!--<add claimType="http://sample.thinktecture.com/claims/office" isOptional="true" />-->
              </claimTypeRequirements>
            </message>
          </security>
        </binding>
      </ws2007FederationHttpBinding>
    </bindings>
    <client />
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <federatedServiceHostConfiguration />
          <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="true" />
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="false" />
          <serviceCredentials>
            <!--Certificate added by FedUtil.  Subject='CN=DefaultApplicationCertificate', Issuer='CN=DefaultApplicationCertificate'.-->
            <serviceCertificate findValue="CB484E11B065E559BB9D7221F0178E3C12848381" storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint" />
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
    <extensions>
      <behaviorExtensions>
        <add name="federatedServiceHostConfiguration" type="Microsoft.IdentityModel.Configuration.ConfigureServiceHostBehaviorExtensionElement, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      </behaviorExtensions>
    </extensions>
    <protocolMapping>
      <add scheme="http" binding="ws2007FederationHttpBinding" />
    </protocolMapping>
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
  </system.webServer>
  <microsoft.identityModel>
    <service>
      <audienceUris>
        <add value="http://localhost:1533/Service1.xamlx" />
      </audienceUris>
      <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
        <trustedIssuers>
          <add thumbprint="7974900A2BB2829BE987C17D2F4503F07C321032" name="http://sample.thinktecture.com/trust/stsdm" />
        </trustedIssuers>
      </issuerNameRegistry>
    </service>
  </microsoft.identityModel>
  <appSettings>
    <add key="FederationMetadataLocation" value="https://identity.thinktecture.com/stsdm/FederationMetadata/2007-06/FederationMetadata.xml" />
  </appSettings>
</configuration>

 

The most important part is the <microsoft.identityModel> section which contains the Windows Identity Foundation setup.

If we run the client now we will see an exception with the following text:

Content Type text/xml; charset=utf-8 was not supported by service http://localhost:1533/Service1.xamlx.  The client and service bindings may be mismatched.

No big surprise as the client isn’t aware yet of the new security measures and still sends a request using the BasicHttpBinding. To get the client to work we need to do an update service reference on the generated proxy. This will generate a new customBinding with all required Windows Identity Foundation settings.

Because the STS Dominick Baier has running supports multiple ways the user can authenticate, either username/password or certificate, we still need to fix the configuration a bit. Locate the <issuedTokenParameters> element used and replace it with the first one from the <alternativeIssuedTokenParameters> block using https://identity.thinktecture.com/stsdm/users/issue.svc/mixed/username address.

Running the client application now results in the following error:

SOAP security negotiation with 'https://identity.thinktecture.com/stsdm/users/issue.svc/mixed/username' for target 'https://identity.thinktecture.com/stsdm/users/issue.svc/mixed/username' failed. See inner exception for more details.

This makes sense as re haven’t specified any user credentials for the client yet. Adding the user student with password abc!123 through the client proxy solves this. The client code now looks like:

static void Main(string[] args)
{
    try
    {
        var proxy = new ServiceClient();
 
        proxy.ClientCredentials.UserName.UserName = "student";
        proxy.ClientCredentials.UserName.Password = "abc!123";
 
        var data = proxy.GetData(42);
        Console.WriteLine(data);
    }
    catch (Exception ex)
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine(ex.Message);
        Console.ResetColor();
    }
 
    Console.ReadLine();
}

And now the client can work just fine with the secured service thanks to the StarterSTS hosted by Dominick.

 

Enjoy!

 

www.TheProblemSolver.nl

Wiki.WindowsWorkflowFoundation.eu