Posted by: dotnetninja | November 12, 2010

Securing WCF REST Service with Azure AppFabric Access Control Service and OAuth(WRAP)

Introduction

There are a lots of code examples of how to create REST services but there are few example of how to secure them. As security is one of the most important things when building any kind of system this should not be undermined. Most of those securing features when using WCF and REST are using SSL, some kind of custom implementations and/or using ASP.NET features. There were some attempts to create OAUTH channels for WCF REST services and there are many types of implementations out there nut the question is what to use, but this is not easy and it is very confusing. How can I (very simple) use for example OAUTH for my WCF REST services?

Handling security in RESTful applications can be done using transport security (SSL), message security (encrypting), authentication (signing messages, tokens) and authorization (which is controlled by service).

When using token based authentication (OAUTH) token is retrieved from service by sending user name and password to the service, in some cases one will also send secret key and/or application key.

If one will use WCF data services (formerly Astoria, ADO.NET data service) this could be done relatively easy but in this case we will try to create an secure WCF REST service using Azure AppFabric Access Control Service.

As we will use Azure AppFabric Access Control here are key features listed from codeplex, AppFabric Access Control:

  • Integrates with Windows Identity Foundation (WIF) and tooling
  • Out-of-the-box support for popular web identity providers including: Windows Live ID, Google, Yahoo, and Facebook
  • Out-of-the-box support for Active Directory Federation Server v2.0
  • Support for OAuth 2.0 (draft 10), WS-Trust, and WS-Federation protocols
  • Support for the SAML 1.1, SAML 2.0, and Simple Web Token (SWT) token formats
  • Integrated and customizable Home Realm Discovery that allows users to choose their identity provider
  • An OData-based Management Service that provides programmatic access to ACS configuration
  • A Web Portal that allows administrative access to ACS configuration

Implementation

Steps that we will perform will look like this:

  1. Configure Windows Azure Access Control via its portal.
  2. Create “secure” local WCF REST service using WCF REST service template and implement custom ServiceAuthorizationManager.
  3. Create test client that will authenticate and retrieve token from Access Control Service, which will be then used by WCF REST service to validate calls. This can be viewed in figure bellow.

image

    Figure 1: Secure WCF REST service with ACS.

Configure Windows Azure Access Control (Define rules, keys, claims)

Administrator will register claims and keys with ACS management service or through Management Portal. ACS will be the one who takes care of safety and will sign all tokens with key that is generated in the ACS.
WCF service has key which is used to validate token received from the client.

I will go through all step to configure ACS via management portal, this can also be viewed in the ACS labs on the codeplex, otherwise go to step 2.

  1. Log in to https://portal.appfabriclabs.com
    ACS1
  2. By pressing project name one will be able to Add Service NameSpace (new page).
    ACS2
  3. Then pressing Access Control link
    ACS3
  4. By pressing Manage button we will be redirected to the Access Control Service main menu. Here we can administrate TOKENS, certificates, groups, claims, and so on.
    ACS4
  5. Press  Relaying Party Applications and Add Relaying Party Application to configure Relaying party (WCF REST Service).
    Here we are going to configure service endpoint which will be “secure”.
    a) Write Name of the Service under Name.
    b) Write URL of the service that will be “secure” (in our case local service)
    c) Under token format I have changed SAML 2.0 to SWT (optional).
    d) Under Identity providers unselect Windows Live ID
    e) Under Token Signing Options press generate to generate new Token signing key, which will be used by our service, so you can copy now this token or take it later.
    ACS12
  6. Press Save and go back to main menu (Look at image under 4.). Press Rule Groups and there should be Default Rule Group for WCF Rest Azure Service(one we created in previous step).
    a) Press  Default Rule Group for WCF Rest Azure Service (or you relaying part name).
    ACS13
    a) Press Add Rule
    ACS14

    b) After pressing Add Rule you will be redirected to another page there you can choose Access Control Service under Claim Issuer. All other can have default values.
    ACS15
  7. Last step if to create an Identity for service that client(consumer) will be using. In main menu (under 4.) press Service Identities and then Add Service Identity. This service identity name will be then used as a login name in the client code. Press Save.
    ACS10
  8. Finally Press Add Credentials and write name and password then save.
    ACS11

Now we can work with our service code. This configuration can also be done programmatically. More information can be found at http://acs.codeplex.com.

Creating Secure WCF REST Service

Create new Windows Azure Cloud Service and add Wcf Web Role. I have used new WCF REST template for that.

In Web.config add values for:

  • Service Namespace (which we created under point 2), in my case armanwcfrest.
  • Hostname for acs.
  • Signing key which we have generated (under point 5-e)
<appSettings>
    <add key="AccessControlHostName" value="accesscontrol.appfabriclabs.com"/>
    <add key="AccessControlNamespace" value="armanwcfrest"/>
    <add key="IssuerSigningKey" value="yourkey"/>
  </appSettings>

Now we need to create custom Authorization manager to authenticate each call to our service. For that purpose we need to create custom class and inherit from SecurityAuthorizationManager.

ServiceAuthorizationManager is called once for each message that is coming to the service (message that service is going to process). ServiceAuthorizationManager will determine if a user can perform operation before the deserialization or method invocation occurs on service instance. For that it is more preferable than using PrincipalPermission which will invoke method and why do that if the user will be rejected. Another advantage of using ServiceAuthorizationManager is that can separate service business logic from authorization logic.

This custom class will do following things:

  • Override CheckAccessCore to check for Authorization HttpHeader
  • Check if that header begins with WRAP
  • Take access_token value and use TokenValidator class (help class provided in code) to validate token.
  • Return true if token validation succeded or false if not.
public class AccessControlServiceAuthorizationManager : ServiceAuthorizationManager
    {
        string serviceNamespace = ConfigurationManager.AppSettings.Get("AccessControlNamespace");
        string acsHostName = ConfigurationManager.AppSettings.Get("AccessControlHostName");
        string trustedTokenPolicyKey = ConfigurationManager.AppSettings.Get("IssuerSigningKey");
        string trustedAudience = "http://localhost:81/Service1/";

        protected override bool CheckAccessCore(OperationContext operationContext)
        {
            string headerValue = WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.Authorization];

            // check that a value is there
            if (string.IsNullOrEmpty(headerValue))
            {
                GenerateErrorResponse();
                return false;
            }
            // check that it starts with 'WRAP'
            if (!headerValue.StartsWith("WRAP "))
            {
                GenerateErrorResponse();
                return false;
            }
            string[] nameValuePair = headerValue.Substring("WRAP ".Length).Split(new char[] { '=' }, 2);

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

            // 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))
            {
                GenerateErrorResponse();
                return false;
            }

            //VALID!
            return true;
        }
        public void GenerateErrorResponse()
        {
        //    WebOperationContext.Current.OutgoingResponse.StatusCode =
        //HttpStatusCode.Unauthorized;
        //    WebOperationContext.Current.OutgoingResponse.StatusDescription = "Unauthorized";
        }
    }

In Global.asax.cs I have created SecureWebServiceHostFactory that will be using AccessControlServiceAuthorizationManager class.

Global.asax.cs

 public class Global : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            RegisterRoutes();
        }

        private void RegisterRoutes()
        {
            // Edit the base address of Service1 by replacing the "Service1" string below
            var securewebServiceHostFactory = new SecureWebServiceHostFactory();
            RouteTable.Routes.Add(new ServiceRoute("Service1", securewebServiceHostFactory, typeof(Service1)));
        }
    }
    public class SecureWebServiceHostFactory : WebServiceHostFactory
    {
        protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
        {
            ServiceHost host = base.CreateServiceHost(serviceType, baseAddresses);
            host.Authorization.ServiceAuthorizationManager = new AccessControlServiceAuthorizationManager();
            return host;
        }

        public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
        {
            ServiceHostBase host = base.CreateServiceHost(constructorString, baseAddresses);
            host.Authorization.ServiceAuthorizationManager = new AccessControlServiceAuthorizationManager();
            return host;
        }
    }

Client Code

  1. Client application will retrive token from Azure AppFabric Access Control Service for specific adress (URL).
  2. It will send message to our service with token inside Authorization header.
  3. Hopefully receive response.

Client.cs

        static string accessControlHostName = ConfigurationManager.AppSettings.Get("AccessControlHostName");
        static string accessControlNamespace = ConfigurationManager.AppSettings.Get("AccessControlNamespace");

        static void Main(string[] args)
        {
            Console.WriteLine("\nPress Enter");
            string valueToReverse = Console.ReadLine();

            string token = GetTokenFromACS("http://localhost:81/Service1/");
            string serviceResponse = SendMessageToService(token);

            Console.WriteLine("Service responded with: {0}\n", serviceResponse);
            Console.WriteLine("Press  to exit");
            Console.ReadLine();
        }
        private static string SendMessageToService(string token)
        {
            WebClient client = new WebClient();
            client.BaseAddress = ConfigurationManager.AppSettings.Get("ServiceAddress") + "123";
            string headerValue = string.Format("WRAP access_token=\"{0}\"", token);

            client.Headers.Add("Authorization", headerValue);

            var serviceResponseBytes = client.DownloadString(String.Empty);
            return serviceResponseBytes;
        }

        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}", accessControlNamespace, accessControlHostName);

            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]);

        }

Fiddler View client requesting token

Fiddler1

Client call with Authorization Token and response from the service.

Fiddler2

Resources:

More information about OAUTH, identity and access control in the cloud can be found in Vittorio Bertocci’s session at PDC 2010.

Documentation about OAUTH.

Rest libraries Hammonck, RestSharp, WCF Web API.

Source Code: RestService

Note: Many thanks to my friend Herbjörn for reviewing this post. Do not miss his presentation on Azure Summit 17-18 Nov (2010)
About these ads

Responses

  1. fantastic walk-through

    • Arman,

      Is there a simple way to interface this type of approach with a javascript request. I am fairly new to this and need a little help.

  2. Where I can download TokenValidator class?

  3. Excellent post!
    Could you upload some high resolution pics for the Fiddler view please. I am trying to test the application using Fiddler and not sure what is wrong.

  4. […] See the article here: Securing WCF REST Service with Azure AppFabric Access Control … […]

  5. Downloading your soruce code doesn’t work. Can you please check it?


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 )

Google+ photo

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

Connecting to %s

Categories

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: