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 client request the ACS to get a Token using some informations (like username, password)
- The client request the Service, using this Token (by adding this Token in the header of the message)
- 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)
- 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!