Azure ACS on Windows Phone 7

Recently I was digging into Azure ACS (Access Control Service) V2, trying to leverage ACS as an authentication mechanism for a Windows Phone application.

I found a sample app on the ACS Codeplex page here. The main architecture of the sample is as follows.

  • WP7 client queries the JSON endpoint for a list of providers and the corresponding login pages.
  • Using a Browser control the user logs in to the provider page which redirects the result to ACS
  • ACS then redirects a token response to a “Relying Party Page”
  • The “Relying Party Page” decodes the token and using a script notify sends the text form of the token back to the client
  • WP7 client saves the text token and attaches an OAuth header to all requests to the server
    Clear as mud? It seems like there are many steps that can be removed from that flow, and also it requires you to have a separate ASP.NET relying party page online which simply performs a script notify. I developed a work around that will eliminate the need for a Relying Party Page.

After posing on the Codeplex project, I found out that there is a parameter that can be set which will cause ACS to send a script notify to the client application. Here are the changes I made to the sample:

ContosoContactsAppSignIn.xaml.cs

SignInControl.GetSecurityToken(new Uri("https://<namespace>.accesscontrol.appfabriclabs.com:443/v2/metadata/
IdentityProviders.js?
protocol=javascriptnotify&realm=http%3a%2f%2fcontosocontacts%2f&reply_to=&context=&request_id=&version=1.0"));

Added ACSResponse JSON Data Contract

[DataContract]
    public class ACSResponse
    {
        [DataMember]
        public string appliesTo { get; set; }
        [DataMember]
        public string context { get; set; }
        [DataMember]
        public long created { get; set; }
        [DataMember]
        public long expires { get; set; }
        [DataMember]
        public string securityToken { get; set; }

        [DataMember]
        public string tokenType { get; set; }

        internal static ACSResponse FromJSON(string response)
        {
            var memoryStream = new MemoryStream(Encoding.Unicode.GetBytes(response));
            var serializer = new DataContractJsonSerializer(typeof(ACSResponse));
            var returnToken = serializer.ReadObject(memoryStream) as ACSResponse;
            memoryStream.Close();

            return returnToken;
        }
    }

AccessControlServiceSignIn.xaml.cs

private void SignInWebBrowserControl_ScriptNotify(object sender, NotifyEventArgs e)
{
    var acsResponse = ACSResponse.FromJSON(e.Value);

    RequestSecurityTokenResponse rstr = null;
    Exception exception = null;
    try
    {
        ShowProgressBar("Signing In");

        string binaryToken = HttpUtility.HtmlDecode(acsResponse.securityToken);
        string tokenText =
RequestSecurityTokenResponseDeserializer.ProcessBinaryToken(binaryToken);
        DateTime expiration = DateTime.Now + TimeSpan.FromSeconds(acsResponse.expires – acsResponse.created);

        rstr = new RequestSecurityTokenResponse
                    {
                        Expiration = expiration,
                        TokenString = tokenText,
                        TokenType = acsResponse.tokenType
                    };

The RequestSecurityTokenResponseDeserializer is from the previous relying party page. However the ACS Response object does not have the same structure as the WS-Federation token so much of the deserializer can be removed. Here is what I have left.

public class RequestSecurityTokenResponseDeserializer
{
    static XNamespace wsSecuritySecExtNamespace = "
http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
    static XName binarySecurityTokenName = wsSecuritySecExtNamespace + "BinarySecurityToken";

    public static string ProcessBinaryToken(string token)
    {
        XDocument requestSecurityTokenResponseXML = XDocument.Parse(token);
        XElement binaryToken = requestSecurityTokenResponseXML.Element(binarySecurityTokenName);

        byte[] tokenBytes = Convert.FromBase64String(binaryToken.Value);
        return Encoding.UTF8.GetString(tokenBytes, 0, tokenBytes.Length);
    }

}

And that is about it. You can download my source code. If you have any questions please comment or email me.

Making this change allowed me to eliminate lots of complexity in the overall architecture of this sample.

Hopefully soon more documentation will be added to the ACS Codeplex project for other gems like the different protocols. Until then I hope this will help.

Zip

3 thoughts on “Azure ACS on Windows Phone 7”

Leave a Reply