Alik Levin's

Clarity, Technology, and Solving Problems | PracticeThis.com 

June, 2011

  • Alik Levin's

    How To Request SWT Token From ACS And How To Validate It At The REST WCF Service Hosted In Windows Azure

    • 0 Comments
    Programming Windows Azure - Programming the Microsoft Cloud

    This post answers the following question:

    “How do I use the Access Control Service (ACS)  and a Simple Web Token (SWT) to secure a REST-based WCF service that is hosted in Windows Azure? How do I build a client application that obtains and uses an SWT token from ACS to connect to this web service based on Username/Password pair?”

    REST WCF SWT ACS UID/PWD

    Summary of steps:

    • Step 1 – Create REST WCF Service
    • Step 2 – Configure ACS To Issue SWT Token
    • Step 3 – Implement Code That Validates The SWT Token At The REST WCF Service
    • Step 4 – Implement Client That Requests SWT Token From ACS And Sends Request To REST WCF Service
    • Step 5 – Deploy To Windows Azure 

    Step 1 – Create REST WCF Service

    In this step you will create REST WCF service that can be hosted in IIS. The REST WCF service will have an SVC file that will be hosted by IIS, the WCF library that has REST interface and actual REST service that implements the interface.

    To create REST WCF service to be deployed in IIS

    Follow the steps outlined in RESTful WCF Architecture – Hosting RESTful WCF Services in IIS.

    Step 2 – Configure ACS To Issue SWT Token

    In this step you will need to configure your REST web service as relying party and also configure service identity. All these configurations are going to be accomplished using ACS management portal. Note, these configurations can be also accomplished programmatically using ACS management service. For more information on using ACS management service consider reviewing Automation content.

    To configure REST web service as a relying party

    Follow the steps in Step 1 – Configure a Relying Party Using the ACS Management Portal from How To: Authenticate with a Username and Password to a WCF Service Protected by ACS. The key difference is that this time the token format should be configured for SWT for your REST web service.

    To configure service identity for the REST web service

    Follow the steps in Step 1 - Add a Service Identity with a Password from  How To: Add Service Identities with an X.509 Certificate, Password, or Symmetric Key

    Step 3 – Implement Code That Validates The SWT Token At The REST WCF Service

    You need to validate incoming SWT token yourself. When the token hits your REST web service the token must be validated for several aspects, mainly the format, signature, and the expiration. In the SOAP/SAML world all that performed by WIF. WIF does not have built in SWT token handler. These are the token handlers that WIF currently supports [from Windows Identity Foundation (WIF) Configuration – Part V (<securityTokenHandlers>)]":

    • Saml11SecurityTokenHandler
    • Saml2SecurityTokenHandler
    • KerberosSecurityTokenHandler
    • WindowsUserNameSecurityTokenHandler
    • RsaSecurityTokenHandler
    • X509SecurityTokenHandler
    • EncryptedSecurityTokenHandler

    To validate SWT token at the REST WCF service

    Use the code provided with the Code Sample: ASP.NET Web Service. Specifically for the SWT token validation you need the following parts:

    • This part checks general formatting of the token and its presence. In the Default.aspx.csPage_Load method:
    // Copyright 2010 Microsoft Corporation
    // Licensed under the Apache License, Version 2.0 (the "License"); 
    // You may not use this file except in compliance with the License. 
    // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0  
    
    // THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR 
    // CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, 
    // INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR 
    // CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
    // MERCHANTABLITY OR NON-INFRINGEMENT. 
    
    // See the Apache 2 License for the specific language governing 
    // permissions and limitations under the License.
    
    
    // get the authorization header
    string headerValue = Request.Headers.Get("Authorization");
    
    // check that a value is there
    if (string.IsNullOrEmpty(headerValue))
    {
        this.ReturnUnauthorized();
        return;
    }
    
    // check that it starts with 'WRAP'
    if (!headerValue.StartsWith("WRAP "))
    {
        this.ReturnUnauthorized();
        return;
    }
    
    string[] nameValuePair = headerValue.Substring("WRAP ".Length).Split(new char[] { '=' }, 2);
    
    if (nameValuePair.Length != 2 ||
        nameValuePair[0] != "access_token" ||
        !nameValuePair[1].StartsWith("\"") ||
        !nameValuePair[1].EndsWith("\""))
    {
        this.ReturnUnauthorized();
        return;
    }
    
    // trim off the leading and trailing double-quotes
    string token = nameValuePair[1].Substring(1, nameValuePair[1].Length - 2);
    
    // create a token validator
    TokenValidator validator = new TokenValidator(
        this.acsHostName,
        this.serviceNamespace,
        this.trustedAudience,
        this.trustedTokenPolicyKey);
    
    // validate the token
    if (!validator.Validate(token))
    {
        this.ReturnUnauthorized();
        return;
    }
    • This part validates cryptographic validness and other security specific aspects. TokenValidator.cs class:
    // Copyright 2010 Microsoft Corporation
    // Licensed under the Apache License, Version 2.0 (the "License"); 
    // You may not use this file except in compliance with the License. 
    // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0  
    
    // THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR 
    // CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, 
    // INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR 
    // CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
    // MERCHANTABLITY OR NON-INFRINGEMENT. 
    
    // See the Apache 2 License for the specific language governing 
    // permissions and limitations under the License.
    
    namespace Microsoft.AccessControl2.SDK.ASPNetSimpleWebsite
    {
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Security.Cryptography;
        using System.Text;
        using System.Web;
    
        public class TokenValidator
        {
            private string issuerLabel = "Issuer";
            private string expiresLabel = "ExpiresOn";
            private string audienceLabel = "Audience";
            private string hmacSHA256Label = "HMACSHA256";
    
            private string acsHostName;
    
            private string trustedSigningKey;
            private string trustedTokenIssuer;
            private string trustedAudienceValue;
    
            public TokenValidator(string acsHostName, string serviceNamespace, string trustedAudienceValue, string trustedSigningKey)
            {
                this.trustedSigningKey = trustedSigningKey;
                this.trustedTokenIssuer = String.Format("https://{0}.{1}/", 
                    serviceNamespace.ToLowerInvariant(), 
                    acsHostName.ToLowerInvariant());
    
                this.trustedAudienceValue = trustedAudienceValue;
            }
    
            public bool Validate(string token)
            {
                if (!this.IsHMACValid(token, Convert.FromBase64String(this.trustedSigningKey)))
                {
                    return false;
                }
    
                if (this.IsExpired(token))
                {
                    return false;
                }
    
                if (!this.IsIssuerTrusted(token))
                {
                    return false;
                }
    
                if (!this.IsAudienceTrusted(token))
                {
                    return false;
                }
    
                return true;
            }
    
            public Dictionary<string, string> GetNameValues(string token)
            {
                if (string.IsNullOrEmpty(token))
                {
                    throw new ArgumentException();
                }
    
                return
                    token
                    .Split('&')
                    .Aggregate(
                    new Dictionary<string, string>(),
                    (dict, rawNameValue) =>
                    {
                        if (rawNameValue == string.Empty)
                        {
                            return dict;
                        }
    
                        string[] nameValue = rawNameValue.Split('=');
    
                        if (nameValue.Length != 2)
                        {
                            throw new ArgumentException("Invalid formEncodedstring - contains a name/value pair missing an = character");
                        }
    
                        if (dict.ContainsKey(nameValue[0]) == true)
                        {
                            throw new ArgumentException("Repeated name/value pair in form");
                        }
    
                        dict.Add(HttpUtility.UrlDecode(nameValue[0]), HttpUtility.UrlDecode(nameValue[1]));
                        return dict;
                    });
            }
    
            private static ulong GenerateTimeStamp()
            {
                // Default implementation of epoch time
                TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
                return Convert.ToUInt64(ts.TotalSeconds);
            }
    
            private bool IsAudienceTrusted(string token)
            {
                Dictionary<string, string> tokenValues = this.GetNameValues(token);
    
                string audienceValue;
    
                tokenValues.TryGetValue(this.audienceLabel, out audienceValue);
    
                if (!string.IsNullOrEmpty(audienceValue))
                {
                    if (audienceValue.Equals(this.trustedAudienceValue, StringComparison.Ordinal))
                    {
                        return true;
                    }
                }
    
                return false;
            }
    
            private bool IsIssuerTrusted(string token)
            {
                Dictionary<string, string> tokenValues = this.GetNameValues(token);
    
                string issuerName;
    
                tokenValues.TryGetValue(this.issuerLabel, out issuerName);
    
                if (!string.IsNullOrEmpty(issuerName))
                {
                    if (issuerName.Equals(this.trustedTokenIssuer))
                    {
                        return true;
                    }
                }
    
                return false;
            }
    
            private bool IsHMACValid(string swt, byte[] sha256HMACKey)
            {
                string[] swtWithSignature = swt.Split(new string[] { "&" + this.hmacSHA256Label + "=" }, StringSplitOptions.None);
    
                if ((swtWithSignature == null) || (swtWithSignature.Length != 2))
                {
                    return false;
                }
    
                HMACSHA256 hmac = new HMACSHA256(sha256HMACKey);
    
                byte[] locallyGeneratedSignatureInBytes = hmac.ComputeHash(Encoding.ASCII.GetBytes(swtWithSignature[0]));
    
                string locallyGeneratedSignature = HttpUtility.UrlEncode(Convert.ToBase64String(locallyGeneratedSignatureInBytes));
    
                return locallyGeneratedSignature == swtWithSignature[1];
            }
    
            private bool IsExpired(string swt)
            {
                try
                {
                    Dictionary<string, string> nameValues = this.GetNameValues(swt);
                    string expiresOnValue = nameValues[this.expiresLabel];
                    ulong expiresOn = Convert.ToUInt64(expiresOnValue);
                    ulong currentTime = Convert.ToUInt64(GenerateTimeStamp());
    
                    if (currentTime > expiresOn)
                    {
                        return true;
                    }
    
                    return false;
                }
                catch (KeyNotFoundException)
                {
                    throw new ArgumentException();
                }
            }
        }
    }

    To separate the security logic from the business logic consider using either WCF pipeline or implementing HttpModule. That way you will intercept incoming requests and validate the SWT token without polluting the code with this security plumbing.

    Step 4 – Implement Client That Requests SWT Token From ACS And Sends Request To REST WCF Service

    You need to write yourself the code that requests SWT token from ACS. In SOAP/SAML world you would use proper binding and also FedUtil wizard to do the work of requesting a token. These do not exist at the moment for REST WCF services.

    To request a SWT token from ACS

    Use GetTokenFromACS method from the Program.cs in the Client project from Code Sample: ASP.NET Web Service.

    // Copyright 2010 Microsoft Corporation
    // Licensed under the Apache License, Version 2.0 (the "License"); 
    // You may not use this file except in compliance with the License. 
    // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0  
    
    // THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR 
    // CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, 
    // INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR 
    // CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
    // MERCHANTABLITY OR NON-INFRINGEMENT. 
    
    // See the Apache 2 License for the specific language governing 
    // permissions and limitations under the License.
    private static string GetTokenFromACS(string scope)
    {
        string wrapPassword = ConfigurationManager.AppSettings.Get("WrapPassword");
        string wrapUsername = ConfigurationManager.AppSettings.Get("WrapUsername");
    
        // request a token from ACS
        WebClient client = new WebClient();
        client.BaseAddress = string.Format("https://{0}.{1}", SamplesConfiguration.ServiceNamespace, SamplesConfiguration.AcsHostUrl);
    
        NameValueCollection values = new NameValueCollection();
        values.Add("wrap_name", wrapUsername);
        values.Add("wrap_password", wrapPassword);
        values.Add("wrap_scope", scope);
    
        byte[] responseBytes = client.UploadValues("WRAPv0.9/", "POST", values);
    
        string response = Encoding.UTF8.GetString(responseBytes);
    
        Console.WriteLine("\nreceived token from ACS: {0}\n", response);
    
        return HttpUtility.UrlDecode(
            response
            .Split('&')
            .Single(value => value.StartsWith("wrap_access_token=", StringComparison.OrdinalIgnoreCase))
            .Split('=')[1]);
    }

    To send GET request the the REST WCF service

    Assuming your REST WCF service implement UriTemplate /users for WebGet  then the following code can be used to issue GET request:

    string token = GetTokenFromACS(realm);
    WebClient client = new WebClient();
    string headerValue = string.Format("WRAP access_token=\"{0}\"", token);
    client.Headers.Add("Authorization", headerValue);
    Stream stream = client.OpenRead(@"http://yourNameSpace.cloudapp.net/RESTfulWCFUsersServiceEndPoint.svc/users");
    StreamReader reader = new StreamReader(stream);
    String response = reader.ReadToEnd();

    Step 5 – Deploy To Windows Azure

    If you:

    then you need to take few extra steps to make sure all assemblies get uploaded to Windows Azure since they are not directly referenced thus not automatically added to the deploy package. These are the steps:

    • Expand bin folder of the the REST WCF service solution.
    • Right click on the library DLL that implements the REST WCF service and select Include In Project option.
    • Right click on the same library and select Properties.
    • In the Properties window, select “Copy if newer” for the “Copy to Output Directory
    • Do the same steps for the HttpModule

    Now you can publish and deploy your REST WCF service to Windows Azure either through Visual Studio or via Windows Azure portal.

    Related Materials

  • Alik Levin's

    Windows Azure AppFabric Access Control Service (ACS): REST Web Services And OAuth 2.0 Delegation

    • 0 Comments

    Scenario

    Following are characteristics of the scenario:

    • RESTful web service requires SWT token.
    • Credentials validated by the same authority that exposes the RESTful web service.
    • RESTful web service is accessed by intermediary and not by the end user.
    • Credentials must not be shared with intermediary.

     Scenario: ACS OAuth Delegation REST

    Solution

    • Use ACS as an OAuth authorization server.
    • Use WIF Extensions for OAuth CTP.

     Solution: ACS OAuth Delegation REST

    Supporting Materials

    Related Resources

  • Alik Levin's

    Windows Azure AppFabric Access Control Service (ACS): WCF SWT/REST OAuth Scenario

    • 0 Comments
    Programming Windows Identity Foundation

    Scenario

    Following are characteristics of the scenario:

    • WCF service that exposes its functionality via REST and requires an issued SWT token based on any type of credentials – username/password pair, symmetric key, or client certificate.

    ACS WCF REST SWT OAuth

    Solution

    To solve this scenario, use ACS with the following configuration:

    • In the app, use either custom Token Handler with WIF pipeline or parse the SWT token yourself.
    • In ACS, use SWT token when configuring relying party (trust) for your WCF service.
    • You can use any credential types – Password, Symmetric Key, or Client Certificates.
    • Configure Service Identity depending on the credential you are using.

    Supporting Materials

    Related Resources

Page 1 of 2 (6 items) 12