Azure Access Control Service – WCF service with symmetric key authentication

Last week, I search to implement a simple scenario that use the Access Service Control (ACS) of Azure to authenticate with a WCF Service. I’ve retrieved a lab in an old Windows Azure Training Kit (WAPTK) of february 2011. In the next release of the WAPTK, the lab is more focus on the Identity Provider (like Live or Google).

For this example, let’s have a look with the basics of the ACS :

The steps are :

  1. The client request the ACS to get a Token using some informations (like username, password)
  2. The client request the Service, using this Token (by adding this Token in the header of the message)
  3. When it receives the request, the service call the ACS to validate the Token (using the same type of credentials, or using the some of the rule define on the ACS Portal)
  4. If the Token is valid, the service send response to the client
To realize this, you can use some of the following code snippet on client side :
Method to get the Token
private static string GetACSToken(string IssuerName, string IssuerKey, string ServiceAddress, string ServiceNamespace, string AcsHostName)
        {
            // request a token from AppFabric AC
            WebClient client = new WebClient();
            client.BaseAddress = string.Format("https://{0}.{1}", ServiceNamespace, AcsHostName);

            NameValueCollection values = new NameValueCollection();
            values.Add("wrap_name", IssuerName);
            values.Add("wrap_password", IssuerKey);
            values.Add("wrap_scope", ServiceAddress);

            byte[] responseBytes = client.UploadValues("WRAPv0.9", "POST", values);

            string response = Encoding.UTF8.GetString(responseBytes);

            return response
                .Split('&')
                .Single(value => value.StartsWith("wrap_access_token=", StringComparison.OrdinalIgnoreCase))
                .Split('=')[1];
        }
Code to add the Token to the Header
proxy = channelFactory.CreateChannel();
        using (new OperationContextScope(proxy as IContextChannel))
        {
            string authHeaderValue = string.Format("WRAP access_token=\"{0}\"", HttpUtility.UrlDecode(acsToken));

            WebOperationContext.Current.OutgoingRequest.Headers.Add("authorization", authHeaderValue);

            // call the service and get a response
            try
            {//Call the service, for the exemple a echo string service
                string outputString = proxy.echostring(userInputString);
            }
            catch (MessageSecurityException ex)
            {
                if (ex.InnerException != null)
                {
                    WebException wex = ex.InnerException as WebException;
                    if (wex != null)
                    {
                        Console.WriteLine("Error: {0}", wex.Message);
                    }
                }
                else
                {
                    throw;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("error = " + e.Message);
            }

            Console.ReadLine();

and on the service side, the aim is to have a class that inherit from AuthorizationManager Class :
 public class ACSAuthorizationManager : ServiceAuthorizationManager
    {
        private TokenValidator validator;
        private string requiredClaimType;

        public ACSAuthorizationManager(string issuerName, string trustedAudienceValue, byte[] trustedSigningKey, string requiredClaimType)
        {
            this.validator = new TokenValidator(issuerName, trustedAudienceValue, trustedSigningKey);
            this.requiredClaimType = requiredClaimType;
        }

        protected override bool CheckAccessCore(OperationContext operationContext)
        {
            // get the authorization header
            string authorizationHeader = WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.Authorization];

            if (string.IsNullOrEmpty(authorizationHeader))
            {
                SetUnauthorizedResponse();
                return false;
            }

            // check that it starts with 'WRAP'
            if (!authorizationHeader.StartsWith("WRAP "))
            {
                SetUnauthorizedResponse();
                return false;
            }

            string[] nameValuePair = authorizationHeader.Substring("WRAP ".Length).Split(new char[] { '=' }, 2);

            if (nameValuePair.Length != 2 ||
                nameValuePair[0] != "access_token" ||
                !nameValuePair[1].StartsWith("\"") ||
                !nameValuePair[1].EndsWith("\""))
            {
                SetUnauthorizedResponse();
                return false;
            }

            // trim the leading and trailing double-quotes
            string token = nameValuePair[1].Substring(1, nameValuePair[1].Length - 2);

            // validate the token
            if (!this.validator.Validate(token))
            {
                SetUnauthorizedResponse();
                return false;
            }

            //Region to validate Action
            //Remove or comment this region to allow all method of your service
            #region validate Action claim
            // check for an action claim and get the value
            Dictionary claims = this.validator.GetNameValues(token);

            string actionClaimValue;
            if (!claims.TryGetValue(this.requiredClaimType, out actionClaimValue))
            {
                SetUnauthorizedResponse();
                return false;
            }

            // check for the correct action claim value
            string requiredActionClaimValue = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RelativePathSegments.First();
            string[] actions = actionClaimValue.Split(',');
            if (!actions.Any(a => requiredActionClaimValue.Equals(a, StringComparison.OrdinalIgnoreCase)))
            {
                SetUnauthorizedResponse();
                return false;
            }
            #endregion
            return true;
        }

        private static void SetUnauthorizedResponse()
        {
            WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized;
            WebOperationContext.Current.OutgoingRequest.Headers.Add("WWW-Authenticate", "WRAP");
        }
    }
and finally here is the code to use this new class in your service:
WebHttpBinding binding = new WebHttpBinding(WebHttpSecurityMode.None);

            WebServiceHost host = new WebServiceHost(typeof(MyEchoService));
            host.AddServiceEndpoint(typeof(IMyEchoService), binding, new Uri("http://localhost:7000/Service/Default.aspx"));

            host.Authorization.ServiceAuthorizationManager = new ACSAuthorizationManager(
                string.Format(IssuerName, ServiceNamespace),
                Audience,
                Convert.FromBase64String(TokenPolicyKey),
                RequiredClaimType);

            host.Open();

We will see in a future post how to reduce the code when we want to implement this authentication.

Edit : I’ve upload the Lab.docx. If you need more source code, post me an comment!

Advertisement
This entry was posted in Azure, Microsoft, WCF, Windows Azure Platform and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s