This proof of concept consists of a web application which uses WIF’s federated authentication. This web application uses an act-as STS to transform the token before sending it to the Agatha service over https. We use a self-signed localhost certificate in IIS-Express to encrypt all traffic except to the SelfSTS and act-as STS.
The client
In development we used Vittorio’s SelfSTS to access claims. Nice and simple little thing. This is setup by adding a STS reference in your web application project. Like a touch of magic WIF puts the claims on Thread.CurrentPrincipal. Remember to tell the identity model to keep the bootstraptoken.
<microsoft.identityModel>
<service saveBootstrapTokens="true">
(…)
</microsoft.identityModel
The act-as is a custom thing borrowed from a lab in the identity development kit from Microsoft. We also used the signing certificate supplied by them in the same lab
Agatha doesn’t support identity federation out of the box. After much research we ended up writing a channel extension which is hooked into the Agatha endpoint via web.config.
<extensions>
<behaviorExtensions>
<add name="identityStrapperBehaviorExtensionElement" type="ChannelInit.IdentityStrapperBehaviorExtensionElement, ChannelInit" />
</behaviorExtensions>
</extensions>
This extension is added to the behavior used by the client Agatha endpoint. What ChannelInit does, we’ll get to in a moment.
<endpointBehaviors>
<behavior name="EndpointBehavior">
<clientCredentials>
<serviceCertificate>
<defaultCertificate storeName="My" storeLocation="LocalMachine" findValue="CN=localhost" />
</serviceCertificate>
</clientCredentials>
<identityStrapperBehaviorExtensionElement />
</behavior>
</endpointBehaviors>
We had some issues with the DNS when using certificates. That is why the identity element was added. In a distributed environment this has to be a FQDN.
<client>
<endpoint address="https://localhost:44300/ServiceRequiringClaims.svc" binding="customBinding" bindingConfiguration="TestCustomBinding" contract="Agatha.Common.WCF.IWcfRequestProcessor" name="agathaEndpoint" behaviorConfiguration="EndpointBehavior">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
</client>
The act-as uses a ws2007HttpBinding binding. The Agatha service is given a custom binding.
<bindings>
<ws2007HttpBinding>
<binding name="ActAsBinding">
<security mode="Message">
<message establishSecurityContext="false" />
</security>
</binding>
</ws2007HttpBinding>
<customBinding>
<binding name="TestCustomBinding" receiveTimeout="00:05:00" sendTimeout="00:05:00" openTimeout="00:05:00">
<security authenticationMode="IssuedTokenForCertificate" messageSecurityVersion="WSSecurity11WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10">
<issuedTokenParameters tokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1">
<issuer address="http://localhost:55502/Issue.svc" binding="ws2007HttpBinding" bindingConfiguration="ActAsBinding" />
<issuerMetadata address="http://localhost:55502/Issue.svc/mex" />
</issuedTokenParameters>
</security>
<httpsTransport />
</binding>
</customBinding>
</bindings>
The service
We use a basic Agatha service (
http://code.google.com/p/agatha-rrsl/). Our service configuration is shown below.
<services>
<service name="Agatha.ServiceLayer.WCF.WcfRequestProcessor" behaviorConfiguration="RequestProcessorBehavior">
<endpoint address="https://localhost:44300/ServiceRequiringClaims.svc" bindingConfiguration="TestCustomBinding" contract="Agatha.Common.WCF.IWcfRequestProcessor" binding="customBinding" >
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
The behavior uses the federated service host configuration which is an extension supplied by the Microsoft identity model.
<behaviors>
<serviceBehaviors>
<behavior name="RequestProcessorBehavior" >
<federatedServiceHostConfiguration />
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<serviceCertificate storeLocation="LocalMachine" storeName="My" findValue="CN=localhost" />
</serviceCredentials>
<serviceSecurityAudit auditLogLocation="Application" serviceAuthorizationAuditLevel="Failure" messageAuthenticationAuditLevel="Failure" suppressAuditFailure="true" />
</behavior>
</serviceBehaviors>
</behaviors>
The service binding has to match the binding configured in the client.
<bindings>
<customBinding>
<binding name="TestCustomBinding" receiveTimeout="00:00:05" sendTimeout="00:00:05" openTimeout="00:00:05">
<security authenticationMode="IssuedTokenForCertificate" messageSecurityVersion="WSSecurity11WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10">
<issuedTokenParameters tokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1">
<issuer address="http://localhost/ActAsSTS/Issue.svc" />
<issuerMetadata address="http://localhost/ActAsSTS/Issue.svc/mex" />
</issuedTokenParameters>
</security>
<httpsTransport />
</binding>
</customBinding>
We have a referanse to the act-as STS using Microsoft identity model.
<microsoft.identityModel>
<service saveBootstrapTokens="true">
<audienceUris>
<add value="https://localhost:44300/ServiceRequiringClaims.svc" />
</audienceUris>
<issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<trustedIssuers>
<add thumbprint="40a1d2622bfbdac80a38858ad8001e094547369b" name="CN=IdentityTKStsCert" />
</trustedIssuers>
</issuerNameRegistry>
<claimsAuthorizationManager type="SecureServiceCommunication.Services.QueryHost.SimpleClaimsAuthorizationManager, SecureServiceCommunication.Services.QueryHost"/>
</service>
</microsoft.identityModel>
ChannelInit
Inspiration for this extension was found in this entry
http://weblogs.asp.net/gsusx/archive/2010/07/02/enabling-wif-actas-via-configuration.aspx. The ChannelInit assembly consists of a few classes.
To create a behavior we extend BehaviorExtensionElement. This behavior extension returns an IdentityStrapperBehavior which implements the IEndpointBehavior interface.
1: public class IdentityStrapperBehaviorExtensionElement : BehaviorExtensionElement
2: {
3: public override Type BehaviorType
4: {
5: get { return typeof(IdentityStrapperBehavior); }
6: }
7: protected override object CreateBehavior()
8: {
9: return new IdentityStrapperBehavior();
10: }
11: }
In this interface we have found use for the methods ApplyClientBehavior and Validate.
1: public class IdentityStrapperBehavior : IEndpointBehavior
2: {
3: public IdentityStrapperBehavior()
4: {
5: }
6: public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
7: {
8: }
9: public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
10: {
11: clientRuntime.MessageInspectors.Add(new MessageInspector());
12: }
13: public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
14: {
15: }
16: public void Validate(ServiceEndpoint endpoint)
17: {
18: var other = endpoint.Behaviors.Find<ClientCredentials>();
19: if (other == null) return;
20: endpoint.Behaviors.Remove(other.GetType());
21: var item = new FederatedClientCredentials(other);
22: endpoint.Behaviors.Add(item);
23: }
24: }
ApplyClientBehavior adds a Class extending the IClientMessageInspector to the ClientRuntime as MessageInspector. This MessageInspector implements the method BeforeSendRequest, which gets the bootstrap token from Thread.CurrentPrincipal. The token is added in a FederatedClientCredentials as an actas token. This again is added to the channel property list.
1: public class MessageInspector : IClientMessageInspector
2: {
3: public void AfterReceiveReply(ref Message reply, object correlationState)
4: {
5: }
6: public object BeforeSendRequest(ref Message request, System.ServiceModel.IClientChannel channel)
7: {
8: var prop = channel.GetProperty<ChannelParameterCollection>();
9: var fedId = new FederatedClientCredentialsParameters();
10: var secToken = (from ident in ((IClaimsPrincipal) Thread.CurrentPrincipal).Identities
11: where ident.BootstrapToken != null && ident.BootstrapToken is SamlSecurityToken
12: select ident.BootstrapToken).FirstOrDefault();
13: fedId.ActAs = secToken;
14: prop.Add(fedId);
15: return null;
16: }
17: }