Adding a Web Service, (also called an Application Programming Interface, or API for short) to an existing web site or desktop application (of the client-server variety) is a great way to enable additional and innovative uses of the data it holds, and also extend its reach to different development platforms such as native Apple and Linux applications, or to native mobile device applications such as those on Apple's iPhone.
Figure 1: Web Services or APIs can be used to easily extend existing applications onto new platforms
There are really only 2 reasons for the need to secure a Web Service or API;
- the data it serves is sensitive in some way – it all needs to be locked away from any un-authorized access, or only certain users can view certain sets of data (such as financial data)
- it allows users to upload data or otherwise modify something, and there's a need to know who did what, or to restrict who can do what.
The code in this article targets .Net frameworks: 2.0 – 4.0
What do we mean by Authentication?
"Authentication is a process that determines the identity of a user. After a user has been authenticated, a developer can determine if the identified user has authorisation to proceed."[1 p734]
To keep this article straightforward, we'll assume that once a user has been authenticated, they're then free to call any Web Service method. Preventing the use of specific Web Service methods, or filtering the data that Web Service methods return depending on a specific user or the users permissions (which is called Authorization) is outside of the scope of this article, and is left as an exercise for the reader (I've always wanted to say that).
Let's start with the default Web Service project code (File > New Project > ASP.Net Web Service Application). We'll be securing the
Listing 1: Default ASP.Net Web Service Application code
The first thing we need to do is provide a way to accept authentication credentials, and we'll do this by requiring a SOAP Header for all of our calls. We could just as easily use parameters to the Web Service methods, but the SOAP Header is the common place to put such authentication information [1 p1317].
Here we're adding the
SecuredSoapHeader object (9-19), creating a public instance of it in the Web Service (32), and adding the
[SoapHeader] directive to the
HelloWorld() method (35):
Listing 2: Adding a SoapHeader and making it required for calls to HelloWorld()
We don't want to require a username and password for every method call, so let's create a simple
AuthenticateUser() method (36-59) that returns an
AuthenticatedToken that can be used by clients in subsequent requests if the username and password are valid:
Requiring Usernames and Passwords every time, or issuing Tokens?
This really depends on the typical use cases of your Web Service.
Usernames and Passwords are great for Web Services or Web Service methods that aren't used so frequently, an example is the status updates on Twitter.com, as the average update is once every 74 days, but reading status updates (tweets) or searching is much more common and doesn't require authentication.
Tokens are great for Web Services that are more session-based where users make lots of queries or updates over a short period of time, or for Web Services that have extended user authentication (like asking for more than just a Username and Password) that you don't want the user to have to fill out every time. An example would be an e-commerce Web Service that holds some form of payment details.
Finally, there's the question of session state. If you require the username and password every time then the Web Service remains state-less and the client becomes almost state-less (the client application will probably ask the user once for their credentials and use them repeatedly), but the Web Service has to authenticate every call. However, If the Web Service issues a Token then it simplifies the Web Service authentication logic, makes the Web Service a little more secure (as we're not passing Usernames Passwords around as often), but means the Web Service and the client have to remember the Token.
Regardless of your decision to require usernames and passwords or issue tokens, if your Web Service is going to be available on a public network such as the internet, you should take the additional step of using Secure Socket Layer (SSL / HTTPS).
Listing 3: Adding the AuthenticateUser() method, which returns a token
We've also added some basic checking to make sure the Soap Header is valid (38-41), and added the private
IsUserValid() method (62), where the actual authentication will happen.
But before we start looking at authentication and the
IsUserValid() implementation, there's one thing left to do - secure the HelloWorld() method:
Listing 4: Securing the HelloWorld() method
We've done that by adding an overloaded
IsUserValid() method (69-79) that accepts the
SecuredSoapHeader object, it's then a single line to call this
IsUserValid() at the beginning of
HelloWorld() (86) to secure its contents.
Here are the results of some simple tests of the Secured Web Service so far:
Figure 2: Testing the Secured Web Service – anyone can get a AuthenticatedToken
As we can see, any old username and password receives an
AuthenticatedToken, which is obviously wrong, so let's look at the different authentication methods to prevent "foo" from getting access to
SQL Membership Provider Authentication
By default, ASP.Net 3.5 uses the built-in SQLMembershipProvider for storing details about registered users.
If you're adding or securing a Web Service as part of an existing ASP.Net website that uses the built-in SQL Membership login and user management web controls, you can skip the 'Setting it up' part as you will already have the necessary bits set up and working.
The only caveat if you are adding a Web Service to an existing ASP.Net website that already uses Forms Authentication, is that you will need to allow un-authenticated users to access the Web Service (otherwise they'll be redirected to your login form). The Web Service Methods will handle the authentication - just add the following to the web.config, then skip to the 'Using the SQL Membership Provider' section:
Setting it up
The first thing we need to do is enable Forms Authentication in the web.config:
Next, we need a database that has the necessary ASP.Net Membership tables, views and stored procedures. If you already have one, you just need to add the connection string to it in the web.config and you're set.
If you don't have one, you're not sure, or you want to play about with it a bit first, here's a quick way to get one created for you (and create a user account to work with):
- Add a new WebForm to the SecuredWebService project, and open it (we'll delete it in a second..)
- From the Toolbox, drag the CreateUserWizard control onto the new WebForm, between the
- Build and run the project – it should launch at the new WebForm page
- Complete the form and hit 'Create User' – you should get a success message
- Delete the WebForm from the project
Figure 3: Using the Create User Wizard to create a user and the SQL Server Membership Database file (ASPNETDB.MDF)
If you now expand the App_Data folder, you will find ASPNETDB.MDF (you may need to hit the 'show all files' button to see it):
Figure 4: ASPNETDB.MDF created for us
Using the SQL Membership Provider
Back to the SecuredWebService code, and there's just one line to change in the
IsUserValid() method (65):
Note that we're only using the Membership.ValidateUser() method, which does the Username and Password verification for us – it doesn't remember these credentials, it just tells us if they're valid or not.
And a re-run of our tests show that we're now secured:
Figure 5: Secured Web Service using the SQL Membership Provider
Taking it further
Returning simple strings is great for illustration purposes, but it doesn't make the Web Service very easy to use from a client point of view. A more structured and consistent response across all of the Web Service methods would be to return XML. Here are a couple of examples that with very little work could be returned from our sample code:
An error message might look like this:
And a successful response might look like this:
We've discussed how adding a Web Service or API to an existing web site or desktop application is a great way to extend the usefulness of application data, and how they can provide a way to extend it to native applications on different platforms.
We've then seen how easy it is to secure application data using ASP.Net Web Services and Forms Authentication with the default, built-in SQL Membership Provider, and discussed the merits of requiring usernames and passwords versus issuing authenticated tokens.
Andrew Freemantle has been working with Microsoft .Net since 2003, and is a Microsoft Certified Applications Developer.
View complete profile here.
Please login to rate or to leave a comment.