Security has often been the neglected stepchild of software
development, but it is increasingly coming under closer scrutiny. As Web Services
are deployed in greater numbers, the security of these access points into an
organizations business processes can no longer be relegated to backseat status.
Generally, you will have three general security scenarios for
a Web Service:
Impersonation is the process of accessing resources as another user.
In this case, it would be an ASP.NET application or Web Service accessing resources
using a users permissions, rather than the default user account under which
ASP.NET runs.
Because .NET Web Services are based upon IIS and ASP.NET,
the security model for Web Services is inherited from these technologies.
However, Web Services will have different considerations than ASP.NET applications
when developing a security strategy. Obviously, Web Services do not have user
interfaces with which users can interact.
You can think of the available security services as layers (see
Figure 1 below). These layers sit upon one another, can be used in various
combinations, and are provided by either IIS or the ASP.NET runtime. These
various security implementations will be discussed in the next section.
Figure 1: Security Layers
IIS Authentication
IIS provides two main security features: filtering based on IP
Address and Windows-based authentication.
IP/DNS Security
The first layer of security that IIS provides is IP/DNS security.
This is where IIS restricts or allows access to requests based upon the IP
address or DNS of the requesting machine. This authentication is always the
first to occur.
IP Security can be set up on a directory or on individual files.
In the IIS management console, right click on the directory or file for which
you wish to set the IP security. Select Properties, and on the Security tab of
the properties sheet you will find a button to edit IP Address and Domain Name
Restrictions. (Note that this option will only be available on server
platforms; for workstations this button will be disabled.) The form that is
presented is the one in Figure 2 below. From here you can enter IP addresses or
ranges, or domain names. You can make your list either inclusive or exclusive,
depending upon your needs.
Keep in mind, however, that if you choose to enter domains names,
IIS will have to perform a DNS lookup, an expensive operation that could impact
the performance of your site. Furthermore, if the DNS lookup fails, the client
will be denied access to the site. Another point to consider is that if the
clients are behind a proxy server or firewall, then only the proxy server's or
firewall's IP address will be available. This will make it impossible to
differentiate between these clients.
One potential use for IP Security could be in a
business-to-business (B2B) scenario, where you know the IP addresses for the
machines with which you will be interacting.
Figure 2: Setting IP Address and Domain Name Restrictions
Windows Security
The next layer of security provided by IIS actually consists of
several different techniques, but is referred to collectively as Windows
Authentication. These techniques are Basic, Digest, Integrated Windows, and
Client Certificates. All of these methods rely on Windows User Accounts (which
are stored in the Access Control List, or ACL); therefore each user must have a
valid Windows account. This requirement limits the usefulness of Windows
security for sites open to the general population.
These Windows security methods can be used with SSL if desired.
However, the use of SSL can slow server response due to the overhead of
encryption, especially if large amounts of data are returned to the client.
If you choose to open your application to users outside your
immediate network, and implement a custom security solution, these Windows
security options would generally not be used. For this reason, we will not
discuss these methods in any more detail. However, one situation where these
methods may be practical is in a B2B setting, where you are exchanging
information with business partners/clients, whose numbers are small enough to
allow setting up accounts for these entities with limited rights, or where you
can manage the use of client certificates. For more information on Windows
security, refer to the references at the end of this article.
Once IIS has authenticated the request, it will pass that request
and an authentication token to the ASP.NET runtime. If the server is set to
accept anonymous users, then the IUSR_machinename account will be passed
as the authentication token; otherwise, a token for the authenticated user will
be passed.
ASP.NET Security
The last layers of security are provided by ASP.NET. To
understand how this works, lets explore the process in more detail. If and when
IIS succeeds in authenticating a user, it will hand an authentication token to
ASP.NET along with the request. As explained in the previous section, this
token will be based on the type of security IIS is enforcing. The first thing
that ASP.NET will do is to verify that the user represented by the
authentication token has access rights to the requested resource (e.g., aspx or
asmx file), by looking up the user's access rights in the ACL. The user will
either be the users actual account if authentication is enabled in IIS, or the
ISUR_machinename account for anonymous access. This check will be performed
regardless if ASP.NET Impersonation is enabled or not. If this check succeeds,
the ASP.NET runtime will hand the request to the ASP.NET application for
further processing.
ASP.NET will now create two objects, the WindowsPrincipal and
User objects, and attach them to the request. The WindowsPrincipal object
contains information about the user under which the ASP.NET application code is
running, and the type of authentication that was performed. The User object
contains information on the User that originated the request. These values are
determined by the authentication type and by whether Impersonation is on.
Impersonation can be turned on by making the following entry in
the web.config file:
|
<configuration>
<system.web>
<!- - turn on impersonation; default is false - ->
<identity impersonate=true/>
</system.web>
</configuration>
|
If Impersonation is on, then the ASP.NET code will run under
the User account, and be bound by that user's rights on the system. Otherwise,
the code will run under the default user for ASP.NET applications: ASPNET.
See the following table for possible combinations of which user account is
populated in the WindowsPrincipal and User objects, and which is used by ASP.NET
to perform the ACL resource check.
|
Authentication Method
|
Windows Principal (code runs as)
|
User Identity
|
Identity used for ACL Check
|
|
Anonymous + Impersonation
|
IUSR_machinename
|
Null
|
IUSR_machinename
|
|
Anonymous + No Impersonation
|
ASPNET
|
Null
|
IUSR_machinename
|
|
Basic Authentication + Impersonation
|
User Account
|
User Account
|
User Account
|
|
Basic Authentication + No Impersonation
|
ASPNET
|
User Account
|
User Account
|
Now, taking this all into account, we can take a look at how IIS
and ASP.NET work together to authenticate and authorize a request. Figure 3
shows the decision path that IIS and ASP.NET will follow for each request,
based upon the security settings for your application.
Figure 3: Authentication and Authorization Decision Path
Forms Authentication
Forms Authentication is provided by ASP.NET, mainly for Web
applications. Basically, Forms Authentication checks each user request for
a special authentication cookie before giving it access the requested resource.
If it is not present, the user will be redirected to a login form specified
with the web.config file. Once the user has been authenticated, the authentication
cookie is provided, and will be supplied by the client on all subsequent requests.
Forms Authentication can, however, be used by Web Services as
well, with a few minor modifications. With these modifications, Forms
Authentication becomes a custom authentication, with some of the legwork
handled by ASP.NET. Like most forms of custom authentication, you will be able
to authenticate and validate your users against any data store, and include
custom logic as necessary. And because Forms Authentication uses HTTP calls, it
can be used in conjunction with SSL. This is an important point; without the
use of SSL, all information, including user ids and passwords, will be
transmitted in plain text. Therefore, it is common to have at least the login
page (or method) secured with SSL.
Forms Authentication is dependent upon cookies. Without cookies
being supported and enabled, clients can't be properly authenticated. An advantage
of this authentication method is that much of the cookie handling is done
for you. It will encrypt and decrypt the cookies automatically, once the proper
settings have been made in the web.config file. Furthermore, Forms Authentication
provides methods to read and write the authentication cookies.
As mentioned above, several adjustments must be made to use
Forms Authentication for Web Services; these must be made in the web.config
file. The first section to update is the "authentication" element.
The mode attribute must be set to "Forms." Within this element,
a "forms" element must be added. The name attribute is the
name of the authentication cookie that will be written out to the client.
The loginUrl attribute is the page that the client will be redirected
to if the authentication cookie is not present. In the case of Web Services,
we do not want a redirect to take place; therefore, we will put the name of
our Web Service, so the call will be redirected back to the Web Service. The
protection attribute (possible values are: All, Encryption, Validation,
None) is used to protect the authentication cookie. A setting of 'All' will
perform both validation and encryption. 'Validation' will compare the cookie
against the original content placed in the cookie, and 'Encryption' will encrypt
the authentication cookie using the 3DES algorithm. Only the cookie, however,
is encrypted. If sensitive data needs to be protected, SSL should be used.
The timeout attribute will determine how long the cookie will be valid
after the last request. The default is 30 minutes.
|
<authentication mode="Forms">
<forms name="CookieName" loginUrl="Products.asmx" protection="All"
timeout="60" path="/" />
</authentication>
|
The "authorization" element of the web.config file
must also be updated. We must remove the "allow" element and replace
it with a "deny" element with a users attribute. This will
cause ASP.NET to deny authorization for the specified users. The '?' value
indicates all anonymous users; a '*' would indicate all users.
|
<authorization>
<deny users="?" />
</authorization>
|
Now that we've configured ASP.NET to support Forms Authentication
for our Web Service, let's take a look at the Web Service itself.
The first thing that we will do is to import the
System.Web.Security namespace; this will give us access to the Forms
Authentication objects.
|
Imports System.Web.Security
|
Next, we will look at the Login method. This method must be
called by the client before any other methods on this Web Service can be called.
The Login method will place an authentication token cookie on the client,
which will be checked by the other methods in the Web Service before they
run.
|
<WebMethod()> Public Function Login(ByVal UserName As String, ByVal
Password As String) As Boolean
If UserName.Length > 0 And Password.Length > 0 Then>
FormsAuthentication.SetAuthCookie(UserName, True)
Return True
Else
Return False
End If
End Function
|
This sample code uses a very simplistic method for validating
the user's UserName and Password, merely checking that their length is greater
than 0. In a production application, this could be replaced with a check against
a database or any other data store where user account information is kept.
If the validation is successful, the SetAuthCookie method of the FormsAuthentication
object (from the System.Web.Security namespace imported above) is used to
add an authentication cookie to the clients cookie collection. The second
parameter is used to specify whether the cookie is persistent, that is, if
it lives beyond the current browser session.
Note that since the token is sent back as a cookie, the client
must support cookies and have them enabled. This is not an issue for browser
clients, but rich win32 clients do not support cookies by default. We will
address this concern in the next section, which discusses the client
application.
Now let's look at a function within the Web Service that contains
business logic we wish to protect. This is the method for which we implemented
security in the first place. The function returns a product name given a product
ID. The first thing the function does is to verify that the user has been
authenticated. If the user has already run our login procedure, then Context.User.Identity.IsAuthenticated
will be True, so we can run our logic to return the product name. Otherwise,
we can return an error code or message, indicating that the user has not been
authenticated.
The User property of the Context (or HTTPContext) object gets
or sets security information for the current HTTP request. The Identity property
is the Identity object associated with the current principal. The Context.User.Identity
object also exposes a AuthenticationType property, which contains the authentication
type for this user. The AuthenticationType would be 'Forms' in this example.
|
<WebMethod()> Public Function ProductName(ByVal ProductID As String) As
String
If Context.User.Identity.IsAuthenticated = True Then
Return "Pet Rock"
Else
Return "Need to Login"
End If
End Function
|
One other method I have included in this Web Service is the
Logout method. This method will remove the authentication cookie from the
client machine, and can be used to increase the amount of control we have
over the lifetime of the user's authenticated session. Otherwise, the lifetime
of the cookie would be dictated by the SetAuthCookie method called in Login,
and would either be permanent, or would expire when the browser session ended.
|
<WebMethod()> Public Function Logout()
If Context.User.Identity.IsAuthenticated = True Then
FormsAuthentication.SignOut()
End If
End Function
|
Client Application
Let's take a quick look at our rich client. It is a simple
Winforms application that consumes our Web Service. Each of the buttons on
the form will call one of the methods our our Web Service.
Figure 4: Win32 Client for the Products Web Service
Because our Web Service depends on cookies to track authenticated
users, we need to add the ability for our application to handle cookies. To
do this, we need to reference the CookieContainer namespace:
|
Imports System.Net.CookieContainer
|
The cookiecontainer object is used to hold our cookie collection.
Because our application will need this object over its lifetime, not just
for the lifetime of a single method, the cookiecontainer is declared globally
for the form. We will also need to create an instance of our Web Service.
To do this, we will first need to add a Web Reference to our client application
by right-clicking the client project in the Solution Explorer, and selecting
the Add Web Reference menu item. An Add Web Reference dialog will appear;
type the URL of the Web Service into the Address field. Once the Web Service
has been found, and its methods have been listed, select the Add Reference
button. This will add the Web Reference to your project, and in so doing,
create several files. These include wsdl and disco files, as well as a proxy
file, named Reference.vb by default. You can rename the Web Reference, and
in this case, our Web Reference has been named FormsAuth. This will be the
name used when creating instances of the Web Service.
|
Private cookieContainer1 As New System.Net.CookieContainer()
Private wsForms As FormsAuth.Products = New FormsAuth.Products()
|
Because our wsForms variable is of type Web Service, it contains
a property called CookieContainer. We must set this property to the
cookieContainer object that we created declared above. This code has been
placed in the constructor of our form object, so that it is run once at
startup.
|
wsForms.CookieContainer = cookieContainer1
|
Our client application has two buttons: one to run the Web
Service's Login function, and one to run the ProductName function. The code
to run these functions is simple. To run the Login method, we have to pass
a UserName and Password, which we are getting from two TextBoxes on our client
form:
|
Label1.Text = wsForms.Login(TextBox1.Text, TextBox2.Text).ToString
|
Likewise, the ProductName method requires a ProductID argument,
which we are getting from TextBox3:
|
Label1.Text = wsForms.ProductName(TextBox3.Text)
|
By first running the Login method, and then the ProductName
method, you will be able to see the appropriate output on the screen. If
however, the ProductName method is run without first being authenticated, you
will see a message indicating that you need to login.
Summary
This article explains the basics of IIS and ASP.NET security,
and shows how Forms Authentication can be used to help secure Web Services.
While this article does not discuss them, other methods of securing Web Services
can be used, such as: