Vittorio Bertocci

Scatter thoughts

A RESTful CardSpace: sending tokens using the new WCF AJAX Services in Orcas

A RESTful CardSpace: sending tokens using the new WCF AJAX Services in Orcas

  • Comments 4

In short: this is the description of a sample that sends a CardSpace-obtained token to an AJAX service implemented with the new Orcas features.

Few posts ago I published a tutorial about using CardSpace with Silver. While talking about it with Kushal Shah from the Workflow team, he suggested that it could be nice if we'd also demonstrate how to use CardSpace with the new RESTful capabilities of WCF: that sounded perfect for my "cardspace+<technology_of_choice>" series, hence I promply jumped on the task. The post below documents the results.

Preamble

Before diving into the code, let's take a moment for understanding what is this all about.

The .NET framework 3.5, currently in beta, extends WCF with new capabilities explicitly designed to enable web development scenarios. There's really a lot to say on the subject, however for our context it is enough to say that you can now expose WCF services in ways that makes them extremely easy to consume from web pages. In practice, this mean that you can 1) invoke WCF services via HTTP verbs (POST and GET)  and 2) handle messages in web-friendly formats, such as JSON. The macroscopic implication is that you don't need a proxy. Calling a WCF service becames a simple exercise in javascript: you gather the data from whatever UI element you need to, you create "by hand" a web request in AJAX style (with the object XMLHttpRequest or the activeXs Msxml2.XMLHTTP/Microsoft.XMLHTTP) and finally you use the results for updating selcted parts of your page. Those new features can have profound implications on the way in which you develop the backend for a website: you finally have at your disposal an extremely powerful service API, with its expressive model and all its hosting tuning features (think management and throttling), that you can easily use for exposing your asset for web consumption. We can finally stop twisting the arm of web servers for making them work in ways they were not designed for, and we will again able to think in term of content and services without mixing the two in a grey goo difficult to design/maintain. You will hear much more about this from this blog in the near future. In the meanwhile, I wholeheartedly suggest checking out SteveM and Justin blogs.

That said: I would like to show you what you need for leveraging CardSpace in this scenario. This is not a "proper" WCF scenario: our client is a browser, hence we can't count on the WS-Security capabilities of wsHttpBinding and all the other cardspace-capable bindings. Since we are working with a web page, we may use the traditional web based cardspace authentication schema: we may impose that before landing on the AJAX enabled page we went through a succesful authentication step. But that would be too easy :-) For the sake of example in our case we assume that the service itself needs to receive a token from cardspace: that may happen when the service we are using needs a token that is different from the one we used for authenticating to the website (example: we authenticated with the web site using a personal card, but the business function offered by the AJAX service we area calling requires a specific managed card from another IP).

The prerequisites for this sample are fairly straightforward. You need Orcas beta1, and it helps to have this cardspace sample and the new WCF samples (note: one time installation procedure needed) from the Orcas Beta1 release.

The Project

Our project will contain a WCF service and a simple web page that will act as client.

For making things more realistic we will host the service under IIS: in fact, my service was written taking inspiration from the PostAjaxService sample (thanks Eugene for the help with that). You will need to create an application or reuse an existing one.

Note that, since we are playing with CardSpace on a website, we will need to enable HTTPS: if you installed this you can simply deploy the files we are creating in the same directory and you can skip to the next section ("The Project").
Otherwise you can take the certificate from that sample (or any other suitable certificate), install it into the LocalMachine store and assign it as the SSL cert of your site via the IIS management console.

The Service

Writing the service is very easy. You can start with a WCF service library, a class library, whatever you like the most. In the end we are going to write only three files: service.cs, service.svc and web.config. They are going to be very similar to what you find in the PostAjaxService sample, you may even start by modifying it. All our cardspace website samples make use of a helper class for processing the token: we will need to add to our project the file TokenProcessor.cs, from the website\CardSpace\App_Code folder in the cardspace website sample.

Let's take a close look to the three files below, with our usual code highlighting style.

service.cs

 There's really nothing in this code that gives away that we want to expose this service via AJAX. It looks like a good ol' WCF service.

using System;

using System.Web;

using System.ServiceModel;

using System.IdentityModel.Claims;

using Microsoft.IdentityModel.TokenProcessor;

 

namespace AService

{

    // Define a service contract.

    [ServiceContract(Namespace = "http://AService")]

    public interface IMyService

    {

        [OperationContract]

        string EchoString(string s1);

    }

 

    public class MyService : IMyService

    {

 

        public string EchoString(string s1)

        {

            try

            {

                if (s1 == null)

                    return ("Token was null");

                //decode from base64

                byte[] decode = Convert.FromBase64String(s1);

                System.Text.Encoding enc = System.Text.Encoding.UTF8;

                string _tokenstring = enc.GetString(decode);

                //deserialize in a Token instance

                Token t = new Token(_tokenstring);

                //extract & return the givenname claim

                return (t.Claims[ClaimTypes.GivenName]);

            }

            catch (Exception ee)

            {

                return (ee.ToString());

            }

        }

    }

}

We have a using clause for accessing some cardspace related constructs such as ClaimTypes (System.IdentityModel.Claims) and another for accessing the Token helper class from TokenProcessor.cs (Microsoft.IdentityModel.TokenProcessor).

The yellow code declares a minimal ServiceContract, with a single method that accepts a string in input and gives back another string in return.

The entire service code is constituted by the implementation fo the method EchoString (all the usual disclaimers about sample code apply).

The parameter s1 represents the token string, as we receive it from the client. The token string is actually an encrypted XML element: as such it contains some characters that cannot be posted "as is" via a POST. At first I tried to encodeURI the token string before sending it to the service: that took care of the illegal characters, but somehow I was unable to decrypt the token; apparently the encoding/decoding process was messing up with the encrypted portions of the string. Hence I decided to base64-encode the entire token string before sending it, and everything worked fine. The turquoise code takes care of DEcoding s1 from base64 back to its original form.

The green code deserializes the token into an instance of the Token class; the operation decrypts the token and implicitly check the signature integrity, however in a real life application you would make a number of explicit checks on it (like if who signed is actually who you were expecting).

The pink code simply extracts the value of the GivenName claim and sends it back.

service.svc

Can a file more uneventful? This is a typical .svc file: it is the mean through which WCF tells to ASP.NET to which class it is supposed to dispatch the calls addressed to this service. Again, no signs of our RESTful intentions

<%@ServiceHost language="c"# Debug="true" Service="AService.MyService" %>

web.config

the web.config is finally where we see some web action:

<?xml version="1.0" encoding="utf-8"?>

<configuration>

  <system.serviceModel>

    <services>

      <service name="AService.MyService">

        <endpoint address="ajaxEndpoint"

            behaviorConfiguration="AjaxBehavior" binding="webHttpBinding"

            bindingConfiguration="AjaxBinding" contract="AService.IMyService" />

      </service>

    </services>

    <bindings>

      <webHttpBinding>

        <binding name="AjaxBinding" messageEncoding="Json">

          <readerQuotas maxBytesPerRead="999999" />

          <security mode="Transport">

            <transport clientCredentialType="None" />

          </security>

        </binding>

      </webHttpBinding>

    </bindings>

    <behaviors>

      <endpointBehaviors>

        <behavior name="AjaxBehavior">

          <enableWebScript />

        </behavior>

      </endpointBehaviors>

    </behaviors>

  </system.serviceModel>

</configuration>

 

The Services/Service section is the usual one: we define a service called AService.MyService, with endpoint ajaxEndpoint. Well, almost the usual one: the binding type, webHttpBinding, is a new feature in Orcas and it is what enables us to expose the service in webby style.

The boxed portion of the file contains the binding configuration:

  • The grey code shows the knob for enabling the JSON encoding.
  • The green code increases the size limit of the incoming paramenters. It should not be strictly necessary, but with Beta1 it is better to add it.
  • The turquoise code tells to our service that it will be accessed via HTTPS, and the syntax is the same one in use in 3.0 for handling transport security. Beware! If your service is accessed from an HTTPS connection, you need to add this element or the call will fail. In other words, you cannot just move the service from an http to an https application and vice versa: what you tell to WCF via config (transport security or no security) should be consistent with the hosting settings.

The teal code shows the parameter you need to  add in the behavior configuration.

 

That's it for the server! Compile the two *.CS files in a DLL (traditionally it's "service.dll") and deploy it in the \bin folder of your web directory; then copy service.svc and web.config in your web directory. The server is already up and waiting for you to call it! Let's write a quick & dirty client to see how it works

The Client

The client is a simple static HTM page, that I'll simply call "Client.HTM". We already know that we will need to base64-encode the token string before sending it to the service: for the example I used the handy base64.js from Tyler Akins (thanks Garrett for the hint).
Client.htm is pretty big, so I am going to split it in 2 parts: first I am going through the sheer HTML, then I'll go thorugh the javascript.

 

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

    <title>Sample WCF AJAX + CardSpace Client</title>

    <object type="application/x-informationcard" name="__CardspaceObject" id="Object1">

        <param name="tokenType" value="urn:oasis:names:tc:SAML:1.0:assertion" />

        <param name="requiredClaims" value="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname

http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier" />

        <param name="optionalClaims" value="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/streetaddress" />

    </object>

    <script language="javascript" type="text/javascript" src="base64.js"></script>

    <style type="text/css">

        ...

    </style>

</head>

<body>

    <h1>

        AJAX client page</h1>

    <h2>

        Which POST a CardSpace token to a WCF Ajax service</h2>

    <form name="sampleForm">

       <label class="style1" onmousemove="GoGetIt();">

1)     Mouse over here for getting a token via cardspace

</label>

       <label class="style1" onmousemove="makeCall();">

2)     Mouse over here for calling the web service

       </label>

       The GivenName claim is:

<input type="text" name="result" />   

       <textarea cols="100" rows="20" id="xmltoken" name="xmlToken"></textarea>

    </form>

 

      

       </body>

</html>

In the <head> element we have the usual CardSpace object (shown inside a box). This is a very common configuration in the cases in which the identity selector is summoned via javascript: this is exactly our case, since we want to obtain a token from cardspace and manipulate it via client side code (as opposed to simply performing a POST of a form containing the cardspace Object element, in which case the processing happens immediatly on the server). Note that we won't make that much manipulation on the client, we will just re-encode the token string and send it to the server (more on that in the description of javascript part below). The name of the Object element, shown in grey, will be useful for referring to the element from the script code.

The gold code shows the inclusion of the base64.js file.

Below we have a form ("sampleForm") which contains our frugal UI.

We have a label associated to the function GoGetIt (in yellow): hovering the mouse pointer over this label will call the GoGetIt function, which will take care of obtaining a token from cardspace. The token string will be stored in the textarea named xmlToken (in turquoise).

We have another label, associated to the function makeCall (in green): hovering the mouse pointer over this other label will invoke our service by sending the token string obtained in the former step. The results of the call (including errors) will be shown in the input area named "result" (in pink).

Let's not take a look to the function GoGetIt:

        function GoGetIt()

        {

            var xmltkn = document.getElementById("__CardspaceObject");

            var thetextarea = document.getElementById("xmltoken");

            thetextarea.value = encode64(xmltkn.value);

        }

This is a slight variation of the classical GoGetIt. Assigning the cardspace element to a variable causes the identity selector to show up, and gives back a string containing the token obtained from the card chosen by the user. Once we obtain the token string we base64-encode it and we store it in our text area. Note that I have chosen this course of action for making the process as visible as possible: in a real application you would probably just send the token directly after having base64-encode it, there's normally no reason for showing it in the UI outside didactic purposes.

The makeCall function is slightly more complex:

function makeCall()

{

//debugger;

 var xmlHttp;

  try {xmlHttp=new XMLHttpRequest();}

   catch (e) {try {xmlHttp=new ActiveXObject("Msxml2.XMLHTTP");}

    catch (e) {try {xmlHttp=new ActiveXObject("Microsoft.XMLHTTP");}

     catch (e) {alert("This sample only works in browsers with AJAX support"); return false;}}}

 xmlHttp.onreadystatechange=function()

 {

  if(xmlHttp.readyState==4)

  {

   document.sampleForm.result.value=(eval(xmlHttp.responseText));

  }

 }

 var url = "service.svc/ajaxEndpoint/EchoString";

 try

 {

    var body = '{"s1":"';

    body = body + document.getElementById("xmltoken").value + '"}';

 

    xmlHttp.open("POST",url,true);

    xmlHttp.setRequestHeader("Content-type","application/json");

    xmlHttp.send(body);

 }

 catch(e)

 {

    alert(e);

 }

}

All the part before the box is fairly typical AJAX-related code; we create xmlHttp according to the capabilities of the browser that is rendering us, and we make sure that we evaluate the results when they are actually available.

Let's concentrate on the code inside the box.

The first line creates the URL that refers to our service, following a very intuitive criteria: it starts with service.svc (in grey), the actual resource on the web server; then there is the endpoint name (in green), as defined by the web.config in the service project; finally we have the method name (in yellow), as defined by the servicecontract.

The turquoise code shows how we build the body of our web request a' la JSON: we are just assigning the base64-encoded token string to the paramenter s1. NOTE: "s1" is exactly the param name we defined in service.cs, and we have to stick with that. Having the parameter name wrong is a very common mistake and it can drive you crazy for hours before you discover it.

The gold code shows how we build the request and perform the call: we establish the HTTP verb we want to use (POST), we set the content type header to "application/json" and we send.

 

That's it. Just copy Client.HTM and base64.js in the virtual directory, and you're almost good to go.

Fixing permissions

Since your code will need to use this SSL certificate for decrypting the incoming token, you have to make sure that the Application Pool user has sufficient privileges over the private key of the certificate. Again, if you are reusing the environment created by the cardspace example you sould be already OK.

You can easily do it by

1) finding the file name of the certificate, using the FindPrivateKey utility in the \bin folder of the aforementioned cardspace sample

2) setting the appropriate permissions on it. For example, if you are on Vista the default user will be NETWORKSERVICE: the command line for setting the permissions would look like

cacls C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\[File Name found in step 1] /E /G NETWORKSERVICE:R

NOTE: the documentation on MSDN and on other samples omits the "/E" flag, and the result is that you substitute the entire ACLs sometimes with unwanted consequences (SYSTEM and Administrators lose access rights to the file, to name one): the "/E" flag solves the problem. Thanks Rafael for finding it out :-)

Test Run

The moment of the truth :-) let's fire a browser and point it to Client.HTM:

Very unimpressive, but exactly what we were expecting to see (including the reassuring green address bar). Let's hover on the first label:

Usual CardSpace prompt. Let's click Send.

The text area now contains the base64 encoding of our token string. Let's hover on the second label to send it to the web service:

And, as expected, we get back the value of the givenname claim as proof that the service correctly received and processed the token.

Summary

 We have seen a basic example of how to expose a WCF service as an AJAX service with the new web capabilities introduced in Orcas. We have also seen how to secure the service via HTTPS and how to collect & send CardSpace tokens to it. If you have questions, do not hesitate to ask.

Note for the lazy readers: like the last time, I will probably publish the solution files in some later post... but I won't do it immediately, just for encouraging you to play with it on your own :-) happy exploring!

Page 1 of 1 (4 items)
Leave a Comment
  • Please add 4 and 4 and type the answer here:
  • Post