REST, WCF 4, Forms Authentication, and Custom Clients (Part 3 of 3)


In the first and second parts of this post, I described what makes a REST service different from a SOAP service and how to use WCF to create one. In this post, we’ll look at what a REST client may look like and add some security around the service.

Security

In most line-of-business services, some sort of security system is usually required to prevent unauthorized access to the service’s data. One of the easiest ways to accomplish this is to use the ASP.NET Membership Provider and its associated services. In our example, I’ll be using the NuGet package called “ErikEJ.SqlCeMembership" to quickly create a database of users; without a full SQL Server instance. Once the NuGet package is installed, build the project and navigate to Project > ASP.NET Configuration to setup your users. Make sure to create at least one group and one user. I’ve called my group “Users” and my user, “user1.”

[more]

On the Customer Service, you should add the PrincipalPermission attribute to the methods you want to protect from unauthorized access and demand the user be a member of your role.

   1: [PrincipalPermission(SecurityAction.Demand, Role = "Users")]
   2: public Customer GetCustomer(int customerId)
   3: {
   4:     return _Customers.Where(c => c.CustomerId == customerId).SingleOrDefault();
   5: }

Additionally, you need to add the AspNetCompatibilityRequirements attribute to the service class and require compatibility. Along with that, you will need to assign the user from the http context, to the current thread’s current principal; in the constructor of the service.

   1: [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
   2: public class CustomerService : ICustomerService
   3: {
   4:     public CustomerService()
   5:     {
   6:         Thread.CurrentPrincipal = HttpContext.Current.User;
   7: ...

If you want to know more about the ASP Membership Provider and associated services, you should visit msdn.microsoft.com/en-us/library/ms731049.aspx to learn about the basics.

After your users are setup, you need to create a service your clients can use to authenticate with. ASP.NET has a service, specifically for this, but you have to enable it. To do this, make the following changes to the Web.config file:

Add

   1: <service name="System.Web.ApplicationServices.AuthenticationService" behaviorConfiguration="AuthenticationServiceTypeBehaviors">
   2:         <endpoint contract="System.Web.ApplicationServices.AuthenticationService"
   3:         binding="basicHttpBinding"
   4:         bindingConfiguration="userHttp"
   5:         bindingNamespace="http://asp.net/ApplicationServices/v200"/>
   6:       </service>

to system.serviceModel / services.

   1: <bindings>
   2:   <webHttpBinding>
   3:     <binding>
   4:       <security mode="TransportCredentialOnly" />
   5:     </binding>
   6:   </webHttpBinding>
   7:  
   8:   <basicHttpBinding>
   9:     <binding name="userHttp">
  10:       <security mode="None" />
  11:     </binding>
  12:   </basicHttpBinding>
  13: </bindings>

to system.serviceModel and

   1: <behavior name="AuthenticationServiceTypeBehaviors">
   2:   <serviceMetadata httpGetEnabled="true"/>
   3: </behavior>

to serviceBehaviors.

Also, add

   1: <system.web.extensions>
   2:     <scripting>
   3:       <webServices>
   4:         <authenticationService enabled="true" requireSSL="false" />
   5:       </webServices>
   6:     </scripting>
   7:   </system.web.extensions>

to the <configuration> root. Please note the the requireSSL attribute is set to false. In a production environment, I highly recommend you set this to true and use an SSL certificate to protect your users’ credentials.

Add a new item to the web project by choosing “Text File” and name it “Authentication.svc" or something similar that ends with the .svc extension. Open the new file and add the following to it:

   1: <%@ ServiceHost Language="C#" 
   2:     Service="System.Web.ApplicationServices.AuthenticationService"
   3:     Factory="System.Web.ApplicationServices.ApplicationServicesHostFactory" %>

The Client

Next, we’re going to create a consumer for our service. To start, create a new project and add a service reference to the new Authentication.svc service.

image

As a client, your application will need to keep track of the authentication cookie that is returned from the authentication service. Normally, this type of thing (cookie management) is taken care of by the browser, but since we’re running outside of the browser, we have to explicitly manage cookies.

The methods you need to pay attention to, on the authentication service, are:

  • Login(string username, string password, string customCredential, bool isPersistent)
  • IsLoggedIn()

The first time you call the authentication service, you need to log in and get a cookie, like this:

   1: using (new OperationContextScope(authService.InnerChannel))
   2: {
   3:     try
   4:     {
   5:         IsLoggedIn = authService.Login(username, password, customCredential, isPersistent);
   6:         Cookies = GetCookies(OperationContext.Current);
   7:     }
   8:     catch (EndpointNotFoundException enfe)
   9:     {
  10:     }
  11: }

After you have an authentication cookie, you should store it, for later use. The GetCookies method is shown below, along with the SetCookies method, which sets a cookie on a service client, so the next call to the service is seen as coming from an authenticated user.

   1: private CookieContainer GetCookies(OperationContext operationContext)
   2: {
   3:     HttpResponseMessageProperty httpResponseProperty =
   4:     (HttpResponseMessageProperty)operationContext.IncomingMessageProperties[HttpResponseMessageProperty.Name];
   5:     if (httpResponseProperty != null)
   6:     {
   7:         CookieContainer cookieContainer = new CookieContainer();
   8:         string header = httpResponseProperty.Headers[HttpResponseHeader.SetCookie];
   9:  
  10:         if (header != null)
  11:         {
  12:             cookieContainer.SetCookies(new Uri(@"http://someuri.tld"), header);
  13:         }
  14:         return cookieContainer;
  15:     }
  16:     return null;
  17: }
  18:  
  19: private void SetCookies(OperationContext oc, CookieContainer cookieContainer)
  20: {
  21:  
  22:     HttpRequestMessageProperty httpRequestProperty = null;
  23:     if (oc.OutgoingMessageProperties.ContainsKey(HttpRequestMessageProperty.Name))
  24:     {
  25:         httpRequestProperty =
  26:             oc.OutgoingMessageProperties[HttpRequestMessageProperty.Name]
  27:             as HttpRequestMessageProperty;
  28:     }
  29:  
  30:     if (httpRequestProperty == null)
  31:     {
  32:         httpRequestProperty = new HttpRequestMessageProperty();
  33:         oc.OutgoingMessageProperties.Add(HttpRequestMessageProperty.Name,
  34:             httpRequestProperty);
  35:     }
  36:     httpRequestProperty.Headers.Add(HttpRequestHeader.Cookie,
  37:         cookieContainer.GetCookieHeader(new Uri(@"http://someuri.tld")));
  38: }

Calling the REST service

The simplest way to call a rest service is with the HttpWebRequest class. It has a very simple method to get an XML stream that can be passed into a serializer. The following code demonstrates how to retrieve an object from the XML stream returned by the HttpWebRequest.

   1: protected T ExecuteTwoWayRequest<T>(string serviceUrl, HttpMethod method, string responseDefaultNamespace = null)
   2: {
   3:     //setup the request
   4:     var request = CreateRequest(serviceUrl, method);
   5:  
   6:     //retrieve the response
   7:     var responseSerializer = new XmlSerializer(typeof(T), responseDefaultNamespace);
   8:     var response = request.GetResponse();
   9:     T result = (T)responseSerializer.Deserialize(response.GetResponseStream());
  10:  
  11:     return result;
  12: }
  13:  
  14: private HttpWebRequest CreateRequest(string serviceUrl, HttpMethod method)
  15: {
  16:     var request = (HttpWebRequest)HttpWebRequest.Create(serviceUrl);
  17:     this.SecurityManager.AddSecurity(request);
  18:     request.Method = method.ToString();
  19:     request.ContentType = "text/xml";
  20:     return request;
  21: }

To retrieve a customer from the service (with the GetCustomer method), you need to create a representation of the customer; on the client side. For our trivial example, the customer may look like this:

   1: public class Customer
   2: {
   3:     public int CustomerId { get; set; }
   4:     public string FirstName { get; set; }
   5:     public string LastName { get; set; }
   6: }

To retrieve the customer, use the following code:

   1: public Customer GetCustomer(int id)
   2: {
   3:     var result = base.ExecuteTwoWayRequest<Customer>(string.Format("{0}?id={1}", ServiceAddress, id), HttpMethod.GET, "http://schemas.datacontract.org/2004/07/RESTDatabaseService");
   4:     return result;
   5: }

Conclusion

In this part of the series, we saw how to add security to a REST service and how to create a client that authenticates and uses the secure REST service. You can download the complete code for the service and the client for a more complete example.