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
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.
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!
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).
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
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
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.
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
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