Sunday, 16 January 2011

Building secure, authenticated WCF services for Windows Phone 7 using the ASPNET membership framework

 

I have done a bit of digging around in order to work out how to "go public" with many of the internal facing services that are offered to our enterprise and applications.

With the exception of 1 or 2 web services that we expose to our partners on a private circuit, we do not have any public web services so our security considerations to-date have been very different.

We use ASPNET membership as the basis for our authorisation and authentication so it makes sense to re-use this for our phone applications. In later posts I will look at how federation can be used to achieve a more seamless login approach.

This particular approach uses the "stateless" model where credentials are passed for every service call. There are other options which I will cover in later posts.

Pre-requisites

1. Admin/sa access to SQL Server 2005 or 2008

2. A VeriSign SSL certificate or a certificate where the root CA is VeriSign (such as a GoDaddy issued cert for use with SSL)

3. Visual Studio 2010 with the Windows Phone 7 emulator and tools

Anyway, I have broken this into stages on the basis that you may already have some of the below in place or ready - that was my situation, if so jump to Step 3.

 

Step 1: Creating the ASPNET membership database

Goal: Create the database, roles and associated stored procedures to support the ASPNET membership framework

I'm using a local instance of SQL Server 2008 R2 but this will work equally well on 2005 or SQLExpress. Launch SQL Server management studio and create a new database, let's call it ASPNET

Run the following command from the Framework libraries. This needn't be in the V4 library as I have been using this since .NET V2.0
C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regsql.exe
Running this without any command line parameters will launch the wizard which keeps things simple

Image(1)

Image(2)

Confirm your settings in the wizard and all of the default tables for the ASPNET membership, roles, personalisation, applications etc. with associated stored procedures have been created for you.

Image(3)

 

Step 2: Tools to manage the ASPNET membership data

Goal: Demonstrate how to configure users and roles within the ASPNET tables

In my case at least, we have developed a custom tool for managing our authentication and authorisation data, but Microsoft (as part of the ASP.NET experience in Visual Studio) have created a nice tool to get you going.

In Visual Studio 2010 (this is also available in 2008) create a new ASP.NET web application. The standard template will create all of the membership tables and stored procedures for you in a local .mdf as well as a simple project with a login control that makes use of the membership framework. We will make use of this standard template later for testing our configuration.

To see the admin UI, simply select "Project--> ASP.NET Configuration" from within VS and this will launch the following admin site - for free!

Image(4)

This points to a local SQLExpress .mdf so  we need to change the configuration slightly to point at our SQL 2008 database.

In the web.config for the site, change the following entries in line with the database that contains your membership schema, for this example it is my local machine "." with a database of ASPNET

Before
  <connectionStrings>
    < add name ="ApplicationServices "
        connectionString=" data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnetdb.mdf;User Instance=true"
        providerName=" System.Data.SqlClient" />
  </connectionStrings>
 
After
 
  <connectionStrings>
    < add name ="ApplicationServices "
        connectionString=" data source=.;Integrated Security=SSPI;Initial Catalog=ASPNET"
        providerName=" System.Data.SqlClient" />
  </connectionStrings>

It is worth reviewing the other entries in the web.config to familiarise yourself with the membership and role provider entries.

If you re-launch the admin tool you can now set up your users and roles. 

 

Step 3 - Create the Users and Roles used in this example

Goal: Create some standard users and roles for this sample

For the purpose of this example, I am going to create the following users

     jon who is an administrator role

     sarah who has a user role

In the Web Site Administration Tool click on the Security tab

Enable Roles and then create the following roles

     admin, user

Go back to the security home and create two users

jon (with an admin and user role)

sarah (with a user role)

the password used will be Complex!

To test this set up, add the following code to the page load event of the web project. I have kept this simple as we are not really too interested in authentication and authorisation for standard asp.net web sites, but it is a good place to start.

        protected void Page_Load(object sender, EventArgs e)
        {
           if (User.Identity.IsAuthenticated)
            {
               if (User.IsInRole("admin" ))
                    Response.Write( "You have admin rights\n");

               if (User.IsInRole("user" ))
                    Response.Write( "You have standard user rights\n" );

            }
           else
                Response.Write( "You need to log in");
        }

By logging in and out of the site using the different usernames you can now see how content can be controlled using authentication (identifying the user) and authorisation (identifying the roles associated with the user).

Image(5)

 

Step 4: Creating a Silverlight Enabled WCF Service

Goal: Demonstrate how Silverlight and WCF work together


Silverlight and moreover  WinPhone7 has a restricted set of WCF capabilities. In order to authenticate the user for a WCF call we have to send the username/password in clear text in the SOAP header. DON'T PANIC. By using https and some simple config all of the heavy lifting is done for us.

1. Add a WCF Service Application project named SimpleService

2. Delete the default Service.svc and IService.cs

3. Add a new item from the Silverlight item templates: Silverlight-enabled WCF Service. Call this Simple.cs

4. Change the Simple.svc code to reflect the following code

namespace SimpleService
{
    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode .Allowed)]
   public class Simple
    {
        [ OperationContract]
       public bool DoWork()
        {
           return true ;
        }
    }
}

It is worth reviewing and digesting the generated WCF config before it is modified...

        < binding name ="SimpleService.Simple.customBinding0">
          < binaryMessageEncoding />
          < httpTransport />
        </ binding>

A custom binding has been created in order to make Silverlight apps faster, essentially this is standard http with binaryMessageEncoding. The binary encoding will typically make the XML on the wire 30-40%  smaller.

At this stage I like to create a unit test. The unit test will simple add a reference to this service to ensure that all is well.

 

Step 5: Re-configure the service to use https

Goal: Set up https on IIS and WCF


This is not complicated but requires a bit of care.

1. Create a new application on your local IIS instance call SimpleService 

Image(6)

2. Open up the Server Certificates and use this to install/import your Verisign issued cert. Note: this must be the cert that contains the PRIVATE key 

Image(7)

3. Click on the website instance where this app is created (Default Web Site in my case) and then click on the bindings link. Associated https: on port 443 with the certificate. You may need to add https if it doesn't already exist.

Image(8) Image(9)

4. Now that we have https enabled, update our Simple service to make use of it. Fortunately this is very easy.

     Modify the web.config binding entry on the service to read httpsTransport

Before

          < httpTransport />

After

          < httpsTransport />

If you have a unit test then update the app.config entry as above and update the address to point at the IIS server (instead of the local project instance). 

You will need to publish the service to the IIS server. 

Once published, now is a good time to try and browse to the service to make sure that it is still working.

The SSL certificate will  be for a public URL, so when browsing to https://localhost/SimpleService/Simple.svc (for example) you will see

Image(10)

To overcome this, add an entry into your hosts file, found in C:\Windows\System32\drivers\etc

127.0.0.1     mywebsite.com

Where mywebsite.com is the URL that the certificate is registered to.

Browsing to https://mywebsite.com/SimpleService/Simple.svc should now take you to your service without complaining

As a checkpoint, use your unit test to call the updated service before continuing.

 

Step 6: Adding the membership provider to the service

Goal: Use configuration to integrate WCF with the ASPNET membership provider

Rather than walk you through this, the inline comments are probably clearer. Key points to note are the membership provider entry and the corresponding reference to it in service credentials section of the binding.

Update your web.config as follows (essentially replace all of the corresponding sections as below).

 

  <connectionStrings>
    <!-- Connection used to point to the ASPNET membership provider database -->
    < add name ="ApplicationServices "
        connectionString=" data source=.;User Id=membershipuser; Password=membershippwd;Initial Catalog=ASPNET"
        providerName=" System.Data.SqlClient" />
  </connectionStrings>

  <system.web>
    < compilation debug ="true " targetFramework ="4.0 "  />
     <!-- Configure the Sql Membership Provider-->
    < membership defaultProvider ="AspNetSqlMembershipProvider " userIsOnlineTimeWindow=" 15">
      < providers>
        < clear />
        < add name ="AspNetSqlMembershipProvider " type=" System.Web.Security.SqlMembershipProvider " connectionStringName=" ApplicationServices"
            enablePasswordRetrieval=" false" enablePasswordReset=" true" requiresQuestionAndAnswer=" false" requiresUniqueEmail=" false"
            maxInvalidPasswordAttempts=" 5" minRequiredPasswordLength=" 6" minRequiredNonalphanumericCharacters ="0 " passwordAttemptWindow ="10 "
            applicationName=" /" />
      </ providers>
    </ membership>
  </system.web>
  <system.serviceModel>
    < bindings>
      < customBinding>
        < binding name ="SimpleService.Simple.customBinding0 ">
          < security authenticationMode ="UserNameOverTransport ">
            <!-- Placeholder. Add attribute for such things as including Timestamp and Authentication modes-->
            < secureConversationBootstrap />
          </ security>
          <!-- Optimal way to transmit compressed XML SOAP messages -->
          < binaryMessageEncoding />
          <!-- Configure https-->
          < httpsTransport />
        </ binding>
      </ customBinding>
    </ bindings>
    < services>
      < service name ="SimpleService.Simple ">
        < endpoint address ="" binding=" customBinding" bindingConfiguration=" SimpleService.Simple.customBinding0"
         contract=" SimpleService.Simple" />
        < endpoint address ="mex " binding ="mexHttpBinding " contract=" IMetadataExchange" />
      </ service>
    </ services>
    < behaviors>
      < serviceBehaviors>
        < behavior>
          < serviceMetadata httpGetEnabled ="true "/>
          < serviceDebug includeExceptionDetailInFaults ="true "/>
          <!-- specify how the credentials are verified -->
          < serviceCredentials>
            <!-- Use the aspnet membership provider-->
            < userNameAuthentication userNamePasswordValidationMode ="MembershipProvider "  membershipProviderName=" AspNetSqlMembershipProvider"/>
          </ serviceCredentials>
        </ behavior>
      </ serviceBehaviors>
    </ behaviors>
    < serviceHostingEnvironment aspNetCompatibilityEnabled ="true "
     multipleSiteBindingsEnabled=" true" />
  </system.serviceModel>

 

Step 7 - Calling the service.

Update your web reference from the unit test and call the service as follows. Note how credentials are passed in through the proxy. To get the required client config it is probably easier to delete and recreate the web reference. Note how credentials are passed in through the proxy in the calling code.

       [ TestMethod]
       public void TestMethod1()
        {
            ServiceReference1. SimpleClient proxy = new ServiceReference1.SimpleClient ();
            proxy.ClientCredentials.UserName.UserName = "jon";
            proxy.ClientCredentials.UserName.Password = "Complex!";
           bool result = proxy.DoWork();
        }

You can now add the same to your Windows Phone app, using the asynchronous WCF approach of course. I created a simple ThisMobile.Phone application and called the https/aspnet service as follows.

namespace ThisMobile.Phone
{
   public partial class MainPage : PhoneApplicationPage
    {
       public MainPage()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler (MainPage_Loaded);
        }

       void MainPage_Loaded(object sender, RoutedEventArgs e)
        {

            SimpleProxy. SimpleClient proxy = new SimpleProxy.SimpleClient();
            proxy.ClientCredentials.UserName.UserName = "jon";
            proxy.ClientCredentials.UserName.Password = "Complex!";
            proxy.DoWorkCompleted += new EventHandler<SimpleProxy.DoWorkCompletedEventArgs >(client_DoWorkCompleted);
            proxy.DoWorkAsync();
        }

       void client_DoWorkCompleted(object sender, SimpleProxy.DoWorkCompletedEventArgs e)
        {
            LoggedInStatus.Text = "Logged id, hurray";
        }

    }
}

Hope this was useful....

Cheers - Jon.

 

References:

patterns & practices: WCF Security Guidance

http://wcfsecurity.codeplex.com/

Configuring Secure Sockets Layer in IIS 7

http://technet.microsoft.com/en-us/library/cc771438(WS.10).aspx

WCF Services and ASP.NET (Hosting side by side)

http://msdn.microsoft.com/en-us/library/aa702682.aspx

WCF, ASP.NET Membership Provider and Authentication Service

http://stackoverflow.com/questions/56112/wcf-asp-net-membership-provider-and-authentication-service

Configuring an ASP.NET application to use Membership

http://msdn.microsoft.com/en-us/library/6e9y4s5t.aspx

How to: Use the ASP.NET membership provider

http://msdn.microsoft.com/en-us/library/ms731049.aspx

Manging shared cookies in WCF

http://megakemp.wordpress.com/2009/02/06/managing-shared-cookies-in-wcf/

Cannot import wsdl:portType

http://forums.silverlight.net/forums/p/117406/335336.aspx

WCF Binary Bindings in Silverlight 3

http://johnpapa.net/silverlight/wcf-binary-bindings-in-silverlight-3/

Whats new with web services in Silverlight 3 Beta

http://blogs.msdn.com/b/silverlightws/archive/2009/03/20/what-s-new-with-web-services-in-silverlight-3-beta.aspx

Consuming web services in Silverlight 3

http://videos.visitmix.com/MIX09/T42F

(Security piece from 30 mins in)

Fiddler and the Windows Phone 7 Emulator

http://blogs.msdn.com/b/fiddler/archive/2010/10/15/fiddler-and-the-windows-phone-emulator.aspx

Access control and Identity on WP7

http://blogs.msdn.com/b/card/archive/2010/11/06/access-control-for-windows-phone-7-apps.aspx

6 comments:

Sébastien said...

Very interesting article. Would it be possible to give us a sample code to test, because I tried your method but I have all the time an error "endpoint not found" when I connect to WCF services with Windows 7 Phone Emulator.

Thanks.

Anonymous said...

mate.. ur sample is not working. I got this error:

Could not find a base address that matches scheme http for the endpoint with binding MetadataExchangeHttpBinding. Registered base address schemes are [https].

Anonymous said...

@Anonymous

Change your binding to mexHttpsBinding and serviceMetadata to httpsGetEnabled (instead of httpGetEnabled)..

That shud do the magic..

cheers
R.Suharta

Anonymous said...

yes.. out of interest i tried your solution and get the error "endpoint not found" while connecting the localhost service (https://localhost) from wp7 emulator.

Anonymous said...

Hi readers,

for "endpoint not found" error please refer to my post for the fix..

R. Suharta

Anonymous said...

Hi,

I get an error saying that "...TransportSecurityBindingElement which is not supported in partial trust..." when I test my WCF in IE browser.(I have shared hosting)

How can I solve this if I still wish to use ASPNET Membership authentication ?