Implementing a Secure token service with WCF

I decided to write this post in order to show some necessary steps to build a Secure Token Service (STS) with the latest WCF CTP.

There is a lot of messages in the newsgroups from people with problems to implement a solution like this, so they may find this article useful.

The image below illustrates a generic architecture for an application that uses a brokered authentication with a STS.

 

 

 

 

 

The client application is using a customBinding to secure the communication with the STS and a wsFederationHttpBinding to do that with the target service. In this case, the wsFederationHttpBinding includes the token obtained from the STS in the request message for the service.

As you can see in the image, the following steps are performed in order to execute the final service:

 

1. The client application sends a RequestSecurityTokenMessage (RST) to the STS according to WS-Trust specification.

2. The STS receives a RST message, extract some information from it and creates a token. After that, it sends back a RequestSecurityTokenResponseMessage (RSTR) with the new token.

3. The client application sends a request message to the service and includes the token obtained from the STS.

4. The service executes the service and returns the response to the client application. The token is used to build the security claims for the authenticated user before calling the service method.

 

 

WCF configuration for the client application

 

 

<system.serviceModel >

        <client>

            <!-- Endpoint configuration -->

            <endpoint name="clientendpoint" address="http://localhost/WCFSampleService/service.svc"

                binding="wsFederationHttpBinding"

                contract="IHelloWorld"

                behaviorConfiguration="ServiceBehavior"

                bindingConfiguration="ServiceBinding">

                <identity>

                    <dns value="WCFQuickstartServer"/>

                </identity>

            </endpoint>

        </client>

 

        <bindings>

 

            <!-- Binding used to secure the communication with the STS -->

            <customBinding>

                <binding name="UsernameBinding">

                    <security authenticationMode="UserNameForCertificate"

                            requireSecurityContextCancellation ="false"

                            requireSignatureConfirmation="false"

                            messageProtectionOrder ="SignBeforeEncryptAndEncryptSignature"

                            requireDerivedKeys="true">

                    </security>

                    <httpTransport/>

                </binding>

            </customBinding>

 

            <!-- Binding used to secure the communication with the service -->

            <wsFederationHttpBinding>

                <binding name="ServiceBinding">

                    <security mode="Message">

                        <message issuedTokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1" negotiateServiceCredential="false">

                            <!-- Uncomment this section to ask for specific claims to the STS

                            <claims>

                                <add claimType  ="http://schemas.microsoft.com/ws/2005/05/identity/claims/EmailAddress"/>

                                <add claimType  ="http://schemas.microsoft.com/ws/2005/05/identity/claims/GivenName"/>

                                <add claimType  ="http://schemas.microsoft.com/ws/2005/05/identity/claims/Surname"/>

                            </claims>

                            -->

 

                            <!-- Information related to the Secure token service -->

                            <issuer address="http://localhost/WCFSecurityTokenService/service.svc" bindingConfiguration="UsernameBinding"

                                binding="customBinding">

                                <identity>

                                    <dns value="WCFQuickstartServer"/>

                                </identity>

                            </issuer>

                        </message>

                    </security>

                </binding>

            </wsFederationHttpBinding>

        </bindings>

        <behaviors>

            <!-- Credentials configuration -->

            <behavior name="ServiceBehavior">

                <clientCredentials>

                    <serviceCertificate>

                        <defaultCertificate findValue="CN=WCFQuickstartServer" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectDistinguishedName"/>

                        <authentication revocationMode="NoCheck" certificateValidationMode="None"></authentication>

                    </serviceCertificate>

                </clientCredentials>

            </behavior>

        </behaviors>

 </system.serviceModel>

 

 

Some notes about the configuration above:

 

1. The communication between the client and the STS is secured by a UsernameForCertificate binding. That is, the STS expects a UsernameToken as client token (Token used to authenticate the client)  and a X509Certificate as service token (Token used to encrypt and sign the message).

2. The "issueTokenType" attribute in the "message" element specifies the token type expected by the Service. The client application will include that value in the RST message and therefore the STS will know what kind of token it must create. If the STS does not support that kind of token, then it will return a fault message. For this sample, the client application is asking for a SAML token.

3. The "negotiateServiceCredential" attribute in the "message" element specifies if the client must interchange additional messages with the STS in order to negotiate the service certificate. I will give more information about this flag later in the next paragraphs.

4. The "address" attribute in the "issuer" element specifies the address of the STS. WCF also includes a default implementation of a InfoCard STS. In order to use that STS, you must configure the address http://schemas.microsoft.com/ws/2005/05/identity/issuer/self.

5. The claims element is only valid for SAML tokens. It specifies what claims are expected in the token.

 

 

WCF configuration for the STS

 

 

<system.serviceModel>

        <services>

            <service behaviorConfiguration="ServiceBehavior" name="MySecureTokenService">

                <endpoint binding="customBinding" address="" bindingConfiguration="ServiceBinding" contract="IMySecureTokenService"></endpoint>

            </service>

        </services>

        <bindings>

            <customBinding>

                <binding name="ServiceBinding">

                    <security authenticationMode="UserNameForCertificate"

                            requireSecurityContextCancellation ="false"

                            requireSignatureConfirmation="false"

                            messageProtectionOrder ="SignBeforeEncryptAndEncryptSignature"

                            requireDerivedKeys="true">

                    </security>

                    <httpTransport/>

                </binding>

            </customBinding>

        </bindings>

        <behaviors>

            <behavior name="ServiceBehavior" returnUnknownExceptionsAsFaults="false">

                <serviceCredentials>

                    <serviceCertificate findValue="CN=WCFQuickstartServer" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectDistinguishedName"/>

                    </serviceCredentials>

            </behavior>

        </behaviors>

 </system.serviceModel>

 

 

In this case, the STS implementation is in the class "MySecureTokenService" and it exposes the contract "IMySecureTokenService".

The binding configuration is similar to the configuration in the client.

 

 

WCF configuration for the Service

 

 

<system.serviceModel>

        <services>

            <service

                behaviorConfiguration="ServiceBehavior"

                name="SampleService.HelloWorldService">

                <endpoint binding="wsFederationHttpBinding"

                    address=""

                    bindingConfiguration="ServiceBinding"

                    contract="SampleService.IHelloWorld"/>

            </service>

        </services>

        <bindings>

            <wsFederationHttpBinding>

                <binding name="ServiceBinding">

                    <security mode="Message">

                        <message issuedTokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1" negotiateServiceCredential="false">

                            <!--<claims>

                                <add claimType="http://schemas.microsoft.com/ws/2005/05/identity/claims/EmailAddress"/>

                                <add claimType="http://schemas.microsoft.com/ws/2005/05/identity/claims/GivenName"/>

                                <add claimType="http://schemas.microsoft.com/ws/2005/05/identity/claims/Surname"/>

                            </claims>-->

                            <issuer address="http://localhost/SamlSecurityTokenService/SamlTokenIssuer.ashx" bindingConfiguration="UsernameBinding" binding="customBinding">

                                <identity>

                                    <dns value="WCFQuickstartServer"/>

                                </identity>

                            </issuer>

                        </message>

                    </security>

                </binding>

            </wsFederationHttpBinding>

        </bindings>

        <behaviors>

            <behavior name="ServiceBehavior" returnUnknownExceptionsAsFaults="false">

                <serviceCredentials>

                    <serviceCertificate findValue="CN=WCFQuickstartServer" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectDistinguishedName"/>

                </serviceCredentials>

 

            </behavior>

        </behaviors>

 </system.serviceModel>

 

 

Again, the configuration of the wsFederationHttpBinding is identical to the configuration in the client application.

 

 

STS implementation

 

 

The contract for the STS is quite simple and looks as follows:

 

 

[ServiceContract]

public interface IMySecurityTokenService

{

    [OperationContract(Action = "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue",

                      ReplyAction = "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue")]

        Message IssueToken(Message rstMessage);

}

 

 

It exposes one method "IssueToken" for the action "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue" that is part of the WS-Trust specification.

 

 

public class MySecureTokenService : IMySecurityTokenService

{

        public MySecureTokenService()

        {

        }

 

        public Message IssueToken(Message rstMessage)

        {

            RequestSecurityToken rst = RequestSecurityToken.CreateFrom(rstMessage.GetReaderAtBodyContents());

 

            SecurityToken issuedToken = null;

 

            //Code to create the token goes here ......

 

            // setup RSTR

            RequestSecurityTokenResponse rstr = new RequestSecurityTokenResponse();

 

            //attach security token to RSTR

            rstr.RequestedSecurityToken = issuedToken;

            rstr.TokenType = rst.TokenType;

 

            // send RSTR

            rstr.MakeReadOnly();

            Message rstrMessage = Message.CreateMessage(rstMessage.Version, "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue", rstr);

            rstrMessage.Headers.RelatesTo = rstMessage.Headers.MessageId;

 

            return rstrMessage;

        }

    }

 

 

The STS implementation receives a generic message containing the RST and creates a token using that information.
At the end, it returns a message containing the RSTR with the issued token. For this sample, I have omitted the code to build the token since you can create any token there (UsernameToken, SamlSecurityToken, etc)  depending on the value of the property "rst.TokenType".

 

 

Avoiding the service credential negotiation

 

WCF provides a new feature to negotiate the service credentials for a service.

When this feature is turned on, the client does not need to manually configure or specify the service credentials for the service. As a result, the client application interchanges an additional message with the service using a a protocol called SP-Nego.

There is not any documentation or information around for that protocol, so it could be a problem if want to host your service in different platform like WSE. That is not a problem in WCF because the security bindings know how to interpret this message and create a response according to its content.

 

There are two ways to disable this feature in WCF:

 

1. Secure the communication with a customBinding since it does not provide this feature.

2. Turn off the attribute "negotiateServiceCredential" in the "message" element for the bindings wsFederationHttpBinding or wsHttpBinding.

 

UPDATE: The STS implementation code is available in this post http://weblogs.asp.net/cibrax/archive/2006/09/08/SAML-_2D00_-STS-implementation-for-WCF.aspx

23 Comments

  • Pablo,



    Can you provide a zip file with the code or is this from one of the samples included in the WCF examples?

  • Hi Anthony,

    No, it is a custom sample. Send me your mail address if you want the code.



    Thanks

    Pablo.

  • Hi,



    Is it possible to use TCP transport between the client and the service instead of the default Http transport?



    What about callbacks from the server to the client? how is this being handled?



    Thanks,

    Cooly.

  • Hi Cooly,



    It is not possible to use TCP transport, that is why the WCF team renamed the federation binding to &quot;wsFederationHttpBinding&quot;.



    Sorry, I am not sure to understand your question about the callbacks.



    Thanks

    Pablo.

  • Hi,

    Is this sample still good if we are using the May 22 version of WinFx?

    Thanks.

  • Hi Pablo,

    I have implemented your STS sample with the latest CTP - June CTP. The STS works fine and the client can get a SAML security token from the STS. However, the service does not accept the message with the SAML token and throws the exception "The security protocol cannot verify the incoming message." all the time. This exception takes place as the client try to set up a secure session with the service. Do you know to what this exception may be due? Thanks a lot!

    L.Liu

  • Hi, before getting into the federation stuff I wanted to set up a security sample that used the IssuedToken clientcredentialtype

    I've setup a client a server and STS
    i can successfully call the sts directly, however when the infrastructure tries to contact the STS the server raise a security exception
    Supporting token signatures not expected. at
    System.ServiceModel.Security.ReceiveSecurityHeader.ProcessSupportingSignature(


    DO you have any working sample of using the the IssuedToken clientcredentialtype ?

    thank in advance
    best regards
    Enrico Sabbadin

  • Hi Enrico, I do not have any working sample to show a federation scenario. The WCF SDK already provides a sample showing that scenario, I recommend you to take a look there.

  • Hi Pablo,

    I wonder if there is a way to make a STS work like:

    Client sends RST using username token
    STS returns RSTR with a list of values to select from
    Client returns RSTR with a value from the list
    STS sends SamlToken including the selected value

    I was not able to find anything about that :(

    May be you have an idea where i can start from?

    Thanks

  • based on your figure-1, in step 1,can i just use wsHttpBinding with custom username & password validator behavior?

  • in figure-1, can i just use wsHttpBinding with custom username & password validator behavior to replace the original customBinding?

  • In figure-1, is it possible to use wsHttpBinding with custom username & password validator behavior to replace the original customBinding?

  • Hi mmkk,

    Yes, you can use any binding there, the same for the custom username & pasword validator. (My sample shows a custom binding but it can be easily replaced). I only decided to use a custom binding because it can be easily configured with any setting.

    Regards
    Pablo.

  • Hello,

    I've tried to work out this solution, but i didn't work. My application didn't recognize equestSecurityToken.CreateFrom and rstr.MakeReadOnly(); Is it possible for you to send me a working demo?

    Regards Frits

  • Can i get the source as a zip please.

    This is great, something I have been looking for .. Also was wondering if STS can be done using just Windows (kerbrose) and not use x509 certs at all.. if you could you provide some guidance.

    Hits

  • Keep up the god work Pablo. Drop me the code also when you get a chance please ( ramseur[at]gmail[dizot]com )

    It would be gold to have a WCF STS to authenticate clients of any environment to consume our services. I can use http so thats not a problem.

    Regards
    ramseur



  • hi i need source code fort his article

  • Hi Ranju, are you trying to access the service from a web browser ?. This implementation only works if the client application knows how to talk WS-Trust (That could be WCF or any other implementation).

    Thanks
    Pablo.

  • Hi,
    Many thanks in advance
    I need code in zip for plz send it to me
    My mail id is atul.suyal@nechclst.in

  • Hi ,
    I wonder how to enable the username and passowrd authincation , I don't need to use the certificates

  • this sample doesnt work in VS2010, .net 4, iis 7. I converted the example to 4.0, done some fixes that i thought would work (e.g. giving cert access to iis apppool\defaulapppool instead of network service etc.

    any help would be apprieciated.

  • I am looking for a sample code for caching SAML tokens. Can you please provide the sample code with example. The WCF SDK examples does not have cahing SAML tokens.

    Thanks

  • Hi Anand,

    Take a look at the caching implementation made by Travis Spencer,
    http://travisspencer.com/blog/2009/03/caching-tokens-to-avoid-calls.html

    Thanks

Comments have been disabled for this content.