Export (0) Print
Expand All

Supporting HTTP Authentication and Forms Authentication in a Single ASP.NET Web Site

 

Atif Aziz and Scott Mitchell

April 2006

Applies to:
   ASP.NET (1.0, 1.1, 2.0)

Summary: This articles provides an overview of the Mixed Authentication Disposition ASP.NET Module (MADAM), and discusses how it overcomes the limitations of three common authentication schemes used by Web sites (Forms, Basic, and Digest), by allowing a single Web application to support multiple authentication schemes that validate against a common credential store. (19 printed pages)

Click here to download the code sample for this article.

Contents

Introduction
Examining Basic and Digest Authentication
Authentication and Authorization Workflows in ASP.NET
Incorporating MADAM into the HTTP Pipeline
Installing and Configuring MADAM
Conclusion

Introduction

Most Web sites on the Internet that support user accounts store the users' credentials in a database and, when authenticating a visitor, collect the visitor's credentials through an HTML form. This popular authentication model is known as Forms authentication, and due to its prevalence, end-users are comfortable with creating user accounts and providing their credentials through form fields on a Web page. Furthermore, implementing Forms authentication has become substantially easier with ASP.NET 2.0's Membership capabilities.

Despite the many benefits of Forms authentication, there remains one glaring problem—it's completely non-standard. There is no RFC that describes a standard way to implement Forms authentication. In spite of the lack of a standard, most Forms authentication schemes are implemented in pretty much the same way. When a visitor requests a protected resource, he or she is redirected to a standard login page, with a form that typically contains text boxes for the username and password. Once supplied, these credentials are sent for authentication by using a standard HTML form submission. Upon a successful logon, the browser is once more redirected to the originally requested URL.

When visiting a Web site that implements a Forms authentication workflow, a user can make sense of the experience and deduce where to place his or her credentials, and which buttons to click, even though this experience may differ somewhat from site to site. But what if you want to allow some automated program to be able to access the protected resources on your site? For example, imagine a forum Web site that uses Forms authentication. Such a site might include an RSS feed for each forum that contains the ten most recent posts. Additionally, the Web site might offer private forums that only a select subset of users can access. The challenge is providing RSS feeds to these private forums that only the forums' members can access. While you could use Forms authentication to protect these feeds, RSS reader programs are unable to send credentials using this non-standardized approach.

Fortunately, there are two common, standardized Web-based authentication schemes designed to work over the Internet using the HTTP protocol: Basic authentication and Digest authentication, both of which are detailed in RFC 2617. Both of these implementations have similar high-level workflows. When a user requests a protected resource, the Web server responds with a 401 (Unauthorized) status code and a WWW-Authenticate HTTP response header for each authentication scheme supported. Upon receiving the 401 status, the browser displays a dialog box that prompts the user for his or her username and password. Once the user has supplied his or her credentials and clicked OK, the browser makes another request to the same resource, this time passing along the credential information in the Authorization HTTP request header. If the supplied credentials are valid, the Web server returns the requested resource; otherwise, it replies with another unauthorized (401) response.

Nearly all commercial-grade Web applications support one or both of these standardized authentication schemes, including most RSS readers (RssBandit and SharpReader being two of the many), wget, Microsoft Background Intelligent Transfer Service (BITS), Application Center Test, and many others. In fact, even Web-related classes such as WebClient from the .NET Framework's System.Net namespace support both Basic and Digest authentication (but not Forms).

Note   Another popular authentication scheme for IIS-based Web sites is Integrated Windows authentication, which uses Kerberos and NTLM authentication. Kerberos is an open, documented standard, outlined in RFC 1510; the NTLM authentication scheme, however, is not published as an open standard, although the details have been reverse engineered.

Turning back to the forum Web site example, it may make sense to provide both the Forms and Basic (or Digest) authentication schemes for different resources. For example, you could use Forms authentication when authenticating a user for a Web page, but Basic (or Digest) authentication when authenticating for a private RSS feed. By supporting two different authentication schemes, you get the best of both worlds: the private RSS feeds, protected by Basic (or Digest) authentication, can be accessed by an RSS aggregator, while visitors to the site can continue to use the Forms authentication experience to which they are already accustomed. In this scenario, the Forms and Basic (or Digest) authentication schemes would both authenticate against the same credential store, most likely a database. The only difference would be the low-level mechanism for collecting the requestor's credentials.

Unfortunately, ASP.NET allows only a single authentication scheme to be specified for an entire application, of which neither Basic nor Digest is an option. Relying on IIS's implementation of Basic and Digest authentication doesn't help either, since they authenticate against Windows credentials only. To overcome these limitations, an HTTP Module can be created to replace the Forms authentication workflow with the standard HTTP authentication workflow when certain conditions are met. This article presents such an HTTP Module, dubbed Mixed Authentication Disposition ASP.NET Module (MADAM). MADAM is designed to work with existing Web sites that already use Forms authentication, and adds the capability to replace Forms authentication with a standard HTTP authentication scheme for a selected set of resources, user agents, or any other developer-specified criteria.

This article examines how to plug MADAM into an existing Web site and configure it to use a particular authentication scheme under developer-specified conditions. MADAM was created with ease of use and extendibility in mind, and it can be installed and configured within a matter of minutes. What's more, MADAM comes with an implementation of the Basic authentication scheme built from the ground up. In contrast to the Basic scheme available in IIS, MADAM's implementation does not require the use of a corporate Active Directory, and therefore it can work over any custom credentials store, such as a database.

Note   This article assumes that the reader has an understanding of HTTP Modules and their functionality.

Examining Basic and Digest Authentication

Like most authentication schemes, Basic and Digest are implemented at the HTTP protocol level as a challenge-response dialogue using the HTTP headers. When a user agent makes an anonymous request for a protected resource, the Web server returns an unauthorized (401) response, along with a WWW-Authenticate HTTP header that, in addition to indicating the authentication scheme, contains a challenge. If multiple WWW-Authenticate headers are returned (one for each supported scheme), the user agent may choose which of them to responds to, although ideally it should choose the most secure.

Note   Since subtleties may exist between different user agents, it's worthwhile to test your authentication schemes with a variety of user agents.

Following the 401 response, the user agent issues another HTTP request for the same resource it was initially attempting to access, but this time including an Authorization HTTP header that contains a response to the challenge, proving the identity of the user. The actual contents of the challenge and response are specific to each scheme, and they are detailed in RFC 2617 for Basic and Digest.

One characteristic Basic and Digest share is that the WWW-Authenticate header specifies a realm. In the simplest scenario, a Web site has a single protection space—that is, any protected resource on the site uses the same credential store. For more intricate scenarios, though, a Web site may employ multiple protection spaces, but that's beyond the scope of this article. The realm sent in the WWW-Authenticate header informs the user agent which protection space is being used. Furthermore, the RFC indicates that the realm should "be displayed to users so they know which username and password to use." As Figure 1 shows, user agents typically show the realm in the dialog box that prompts the user for their credentials.

Aa479391.madam01(en-us,MSDN.10).gif

Figure 1. Credentials dialog box showing the realm

One item of interest to note is that if the user agent already has the credentials of the user tucked away in a secured cache, it can already include the Authorization HTTP header in its initial request, and therefore save the trouble of being challenged unnecessarily. This is called pre-authentication, and it is precisely what happens with most user agents during a single session. For example, after supplying your credentials in the dialog box shown in Figure 1, the user agent remembers them for a single session (and it may be able to persist them to disk, if you so choose). The credentials are then continually supplied by the user agent on subsequent requests, until the server once more returns a 401 response.

Charting the Basic Authentication Workflow

With Basic authentication, the WWW-Authenticate header returned by the Web server when requesting a protected resource contains the name of the authentication scheme being used (Basic), followed by the realm. To authenticate using Basic authentication, the user agent re-requests the resource, but this time sends along an Authorization header that contains the authentication scheme (Basic), along with the client's credentials—the client's username and password, separated by a colon (:) and base-64 encoded. Note that by repeating the scheme's name in clear-text in the Authorization header, the Web server knows which authentication scheme the user agent chose to negotiate on. While a Web server can issue more than one WWW-Authentication header per active scheme, a client must choose to include one, and only one, Authorization response.

Assuming that the credentials are valid, the Web server will return the contents of the requested resource to the user agent. For future requests to URLs at the same path or deeper, the user agent can assume the same protection space (realm), and therefore preemptively send the Authorization header along with the credentials. Remember, this is called pre-authentication. The entire workflow is illustrated in Figure 2.

Aa479391.madam02(en-us,MSDN.10).gif

Figure 2. Basic authentication workflow

A shortcoming of Basic authentication is that the client's credentials are sent over the wire in plain-text. Unless the user agent is communicating with the Web server over a secure channel, Basic authentication poses a security risk. Of course, Forms authentication exhibits the same potential security hole, because the form fields in an HTML form are sent over the wire in plain-text as well. In short, use SSL to confidentially transmit credentials with Basic or Forms authentication.

Exploring the Digest Authentication Workflow

Digest authentication was designed to avoid sending a password in an unencrypted form, by employing a much more involved challenge–response implementation than Basic. A complete discussion of the Digest scheme is beyond the scope of this article, and therefore we'll focus on just one key piece here, that being how the credentials transmission is secured.

In a nutshell, the user agent hashes the client's credentials, along with some additional values, by using the MD5 algorithm. This hashed value is then sent back to the Web server in the Authorization header. The Web server then performs the following tasks:

  1. The user's plain-text credentials are retrieved from the credentials store (typically a database for most public-facing Web sites).
  2. These plain-text credentials are hashed by the Web server, along with the additional values, using the MD5 algorithm.
  3. The hashed value computed by the Web server is compared with the hashed value sent by the user agent. If the values match, the request is considered authentic; otherwise, the server responds with an unauthorized response.

The sequence of steps outlined above leaves out a lot of the lower-level details that are present to help further secure the scheme against certain attacks. For example, the WWW-Authenticate header sent back from the Web server must include the authentication scheme to use (Digest), the realm, and a nonce. The nonce is a unique string that is generated each time an unauthorized response is returned. When generating the Authorization header, the user agent must include the nonce in the hash, and also send it back in plain-text. The Digest authentication specification does not specify how the nonce is computed, but often the nonce includes a date and time by which the Authorization response must be received in order to circumvent a category of attacks. Digest implementations can vary in the degree of attention that they pay to these security-related specification particulars.

Figure 3 illustrates the workflow of the Digest authentication specification. Note that, whereas the Basic and Digest authentication schemes differ in the data that's exchanged in the WWW-Authenticate and Authorization headers, both schemes share the same workflow.

Aa479391.madam03(en-us,MSDN.10).gif

Figure 3. Digest authentication workflow

Authentication and Authorization Workflows in ASP.NET

When a request enters the ASP.NET HTTP pipeline, the request proceeds through a number of events. For this article, the three security-related events of interest are as follows:

  • AuthenticateRequest—HTTP Modules can subscribe to this event to participate in the authentication process.
  • AuthorizeRequest—HTTP Modules can subscribe to this event to participate in the authorization process.
  • EndRequest—The last event in the ASP.NET HTTP pipeline; commonly HTTP Modules will subscribe to this event to alter the HTTP status code, headers, or response content before it is flushed to the client.

ASP.NET natively supports three non-standard authentication schemes—Windows, Forms, and Passport—of which only one can be active and specified in the <authentication> element in the Web.config file. Each authentication scheme is responsible for two things:

  1. Initiating the authentication protocol when an unauthorized user requests a protected resource
  2. Validating any pre-authentication credentials sent along with the incoming request

When a user logs onto a Web site that uses Forms authentication, the FormsAuthentication class creates an authentication ticket for the logged-on user. This authentication ticket is an encrypted and hashed cookie that contains the following:

  • The authenticated user's username
  • Additional, optional user information
  • When the ticket expires
  • The ticket issue date
  • Other essential ticket metadata

In ASP.NET 1.x, developers need to explicitly create this ticket in the login page—using the FormsAuthentication.RedirectFromLoginPage() or FormsAuthentication.SetAuthCookie() methods—when the user supplies valid credentials. In ASP.NET 2.0, however, the Login Web control automatically executes the necessary FormsAuthentication methods.

For Forms authentication, the two tasks—initiating the authentication protocol for unauthorized users, and validating pre-authentication credentials—are the responsibility of the FormsAuthenticationModule HTTP Module. To validate the pre-authentication credentials, this module subscribes to the AuthenticateRequest event and inspects the incoming request's cookies for an authentication ticket. If such a ticket exists and has not expired, the module associates the user with the request.

After the AuthenticateRequest event, the AuthorizeRequest event fires. ASP.NET provides two authorization modes:

  • File authorization—Checks against the file system ACLs; this check is handled by the FileAuthorizationModule HTTP Module.
  • URL authorization—Checks against the <authorization> settings specified in the Web.config file; this check is handled by the UrlAuthorizationModule HTTP Module.

If either one of these modules determines that the user (anonymous or authenticated) requesting the resource is not authorized, the request is terminated, and a 401 (Unauthorized) HTTP status is returned as the response. At this point, any active authentication schemes (whether they come from ASP.NET or IIS) are expected to initiate their authentication protocol. For the standard HTTP authentication schemes, this entails appending WWW-Authenticate headers into the 401 response that allow the user agent to learn which authentication schemes are available for proceeding with an authorization request.

If the Web site uses Forms authentication, however, there is no standard WWW-Authenticate header to append. As a result, the FormsAuthenticationModule intercepts the outgoing 401 response and changes it to a 302 (Redirect) to the Web site's login page. Figure 4 illustrates the Forms authentication workflow at the HTTP Module-level, starting with a request by an anonymous visitor for a protected resource.

Aa479391.madam04(en-us,MSDN.10).gif

Figure 4. Forms authentication workflow at the HTTP Module-level

This workflow starts with a request by an anonymous visitor for a protected resource. This request translates into a 401 status by the authorization module, which is then converted into a 302 status by the Forms authentication module. The masking of the 401 status into a 302 status is done on the Web server, before the request is returned to the user agent. Therefore, the user agent never sees the 401 status, only the 302. This 302 status is what causes the user agent to redirect to the login page. After entering his or her credentials, the client receives an authentication ticket through an HTTP cookie, and is redirected to the protected resource. This time, the Forms authentication module determines the user by his or her authentication ticket, and the authorization module allows the user access to the resource.

Incorporating MADAM into the HTTP Pipeline

MADAM's objective is to allow a page developer to indicate that, under certain conditions, Forms authentication should be muted in favor of the standard HTTP authentication protocol. The conditions for when this should happen are specified in the Web.config file. For example, in the forum Web site example discussed earlier, there may exist a page named Rss.aspx that, when passed the querystring ?private=true, returns the latest entries for the private forums for the authenticated user. In such a case, you would configure MADAM to switch off Forms authentication, so that a standard scheme like Basic or Digest can take over. The "Determining When to Switch Authentication Schemes" section discusses the criteria that MADAM uses to decide whether to switch authentication schemes.

The MADAM FormsAuthenticationDispositionModule is an HTTP Module that detects when the Forms authentication workflow is poised to begin, and decides whether or not to inject a different authentication workflow. The way FormsAuthenticationDispositionModule stops Forms is by changing the 302 (Redirect) status code back to the standard 401 (Unauthorized) response, because that's what's expected by user agents conforming to the HTTP authentication standard. Much like how FormsAuthenticationModule replaces the 401 status returned by the authorization module with a 302 status code in order to redirect the user to the login page, MADAM's HTTP module reverses the effect by switching the 302 back to a 401.

After FormsAuthenticationDispositionModule has muted the Forms authentication workflow, an alternate authentication module is then free to take over. MADAM ships with a Basic authentication module, aptly named BasicAuthenticationModule. MADAM has been designed to easily allow other authentication scheme implementations to be used. The "Bringing Digest Authentication Support Into MADAM" section looks at how to incorporate a simple Digest implementation created by Greg Reinacker.

Once Forms authentication is out of the picture, the installed authentication module is responsible for adding any necessary information to the returned request, in order to initialize the standard authentication procedure. For example, both Basic and Digest authentication add a WWW-Authenticate HTTP response header at this stage.

Note   As mentioned earlier, a Web server may implement multiple authentication schemes, sending the client a WWW-Authenticate header for each supported scheme. The user agent will usually select the most secure authentication scheme it supports from those available. For example, Internet Explorer versions that support Integrated Windows Authentication will choose that over Basic or Digest, if a challenge is received from all three. One side effect of this fact, which is discussed in more detail in the "Installing and Configuring MADAM" section, is that MADAM's supplied Basic challenge will be ignored by a user agent if IIS is configured to enable Integrated Windows Authentication for a Web site. For a discussion on this side effect, see the "Disable Authentication Schemes in IIS" section.

Figure 5 illustrates how the Forms authentication workflow is suspended by FormsAuthenticationDispositionModule, and how BasicAuthenticationModule adds the necessary headers to the outgoing response after FormsAuthenticationDispositionModule has done its job.

Aa479391.madam05(en-us,MSDN.10).gif

Figure 5. Suspension of Forms authentication in favor of Basic authentication

Note   The order of the modules in the HTTP pipeline is important, and it is dictated by their ordering in the <httpModules> element in Web.config. Specifically, FormsAuthenticationDispositionModule must handle the EndRequest event after ASP.NET's FormsAuthenticationModule, but before any other authentication module (such as MADAM's BasicAuthenticationModule). In other words, it should sit in between the Forms module and a standard authentication module, in order to orchestrate the disposition of the various schemes.
Imagine what would happen if, in Figure 5, BasicAuthenticationModule was to the right of FormsAuthenticationDispositionModule: on an unauthorized request, BasicAuthenticationModule would see a 302 (Redirect) response and not tack on its WWW-Authenticate header to the outgoing response.

Upon receiving the 401 status and WWW-Authenticate response header from the Web server, the user agent will respond to the challenge by using the client's credentials. If the user agent is a browser, a dialog box may be displayed, to prompt the user for his or her credentials. In the case of the Basic scheme, the credentials are returned to the Web server through the Authorization header. BasicAuthenticationModule parses this header, and authenticates the user against, typically, the same credential store used by the Forms authentication piece. If the user supplied valid credentials, the request is authorized; otherwise, the request is rejected with another 401 response.

Figure 6 shows the sequence of events after those in Figure 5 transpire. Here, the user provides invalid credentials on the first request, and is sent back a 401 response, which re-prompts the user for his credentials. On the second try, the user enters the right credentials, and is granted access to the resource.

Aa479391.madam06(en-us,MSDN.10).gif

Figure 6. User entering incorrect, and then correct credentials

As you can see in Figure 6, the FormsAuthenticationDispositionModule is not involved in authentication of the client's credentials; its sole task is to mute the Forms authentication workflow from starting when an unauthorized request is received.

Although Figure 6 omits FormsAuthenticationModule from the diagram, it is invoked after BasicAuthenticationModule completes its handling of the AuthenticateRequest event. However, since the user has already been authenticated (that is, HttpContext.Request.IsAuthenticated is true), the Forms authentication simply passes on the request, and does not check for an authentication ticket.

At this point, a high-level overview of how MADAM works has been covered, but there are still some nagging questions. For instance, how does FormsAuthenticationDispositionModule decide when to suspend the Forms authentication workflow? Also, how can one get BasicAuthenticationModule to authenticate the supplied credentials against the same credential store used by Forms authentication? These questions are answered in the next two sections, followed by a look at the steps necessary for installing and configuring MADAM on a Web site.

Determining When to Switch Authentication Schemes

The sole responsibility of FormsAuthenticationDispositionModule is to ascertain when to mute Forms authentication and set the response status back to unauthorized (401). With Forms authentication muted, the registered HTTP authentication module will issue a challenge for authenticity. FormsAuthenticationDispositionModule, however, does not blindly mute Forms authentication, but does so only in certain developer-specified scenarios. A request for a particular resource, or from a particular user agent, might be the catalyst for muting Forms authentication.

Rather than trying to hard-code such logic into the HTTP module, MADAM allows the developer to specify the criteria for when to mute Forms authentication. This information is conveyed through discriminators. A discriminator encapsulates a Boolean expression that is evaluated at runtime by FormsAuthenticationDispositionModule, to determine whether or not to mute Forms authentication. More specifically, a discriminator is any type that implements the IDiscriminator interface and its sole Qualifies method. This method is invoked by FormsAuthenticationDispositionModule in order to determine whether or not the discriminator's conditions are met. MADAM ships with two such built-in discriminator implementations:

  • RegexDiscriminator—Evaluates some input expression against a supplied pattern.
  • Discriminator—Provides discrimination based on the evaluation of children discriminators.

These discriminators are expressed through <discriminator> elements in the madam/formsAuthenticationDisposition/discriminators section of the Web.config file, as follows.

<formsAuthenticationDisposition>
  <discriminators all="true">
      <discriminator 
          inputExpression="Request.Url" 
          pattern="ProtectedByBasic\.aspx" type="Madam.RegexDiscriminator" />
  </discriminators>
</formsAuthenticationDisposition>

The top-level <discriminators> element contains an all attribute: when true, Forms authentication is muted only if all child discriminators evaluate to True; when false, Forms authentication is muted if any of the discriminators evaluate to True. In the preceding example, there's only a single RegexDiscriminator in use that evaluates the Request.Url property value against the regular expression pattern ProtectedByBasic\.aspx.

The RegexDiscriminator class evaluates the inputExpression as a data-binding expression that uses the current HttpContext instance as its data source. Therefore, an inputExpression that reads "Request.Url" is evaluated against the pattern, as if you wrote HttpContext.Current.Request.Url in code. If a match of the regular expression pattern is found in inputExpression, the discriminator returns True; otherwise, it returns False. As you can see, the RegexDiscriminator is a very generic mechanism for a handling a wide variety of conditions that can be based on anything in the context of the current HTTP request.

To create more intricate conditions for when Forms authentication should be muted, you can use the Discriminator class to logically group discriminators, as follows.

<formsAuthenticationDisposition>
  <discriminators all="true">
    <!-- This discriminator helps detect redirection to the Forms 
         login page. -->
    <discriminator 
      inputExpression="Response.RedirectLocation" 
      pattern="login\.aspx\?returnurl\="  
      type="Madam.RegexDiscriminator" />
    
    <!-- This discriminator determines when Forms is opted out for 
         an alternative authentication scheme, such as Basic 
         or Digest. -->
    <discriminator type="Madam.Discriminator" all="false">
       <discriminator 
          inputExpression="Request.Url" 
          pattern="/forums/rss\.aspx\?private=true"          
          type="Madam.RegexDiscriminator" />

       <discriminator 
          inputExpression="Request.Url"
          pattern="/SomeOtherPage\.aspx" 
          type="Madam.RegexDiscriminator" />
    </discriminator>
</formsAuthenticationDisposition>

This example uses three discriminators that, in aggregate, mute Forms authentication when an unauthorized request is made for the Forms authentication-protected pages /forums/rss.aspx?private=true or SomeOtherPage.aspx. Since ASP.NET Forms authentication uses, by default, a login page with the name Login.aspx, and tacks on a ReturnUrl querystring parameter, the first discriminator is designed to evaluate to True when a user is being redirected to the Forms authentication login page. On its own, this discriminator would completely replace Forms authentication, because any time an unauthorized visitor attempted to visit a resource protected by Forms authentication, they would be redirected to Login.aspx. When redirected to the login page, though, this discriminator would evaluate to True, and the Forms authentication workflow would be muted. Remember that, in most scenarios, you would want to leave Forms authentication intact, and use an alternate authentication scheme for special cases. Consequently, we'd only want to mute Forms authentication if the request was being redirected to the login page, and if the request was for a URL that needed to be protected by means of an alternate authentication scheme.

Also note that discriminators can be grouped using an element of type Discriminator. The above markup mutes Forms authentication if the first <disciminator> element evaluates to True (that is, a redirection is in effect to login.aspx with a returnurl parameter in the querystring) and at least one of the discriminators in the second <discriminator> element evaluates to True (that is, the request was made to /forums/rss.aspx?private=true, or it was made to /SomeOtherPage.aspx).

Enabling MADAM to Authenticate Against a Custom User Store

With Forms authentication, a user is initially authenticated through a login Web page. In this page, it is the developer's responsibility to implement the logic to collect and validate the user's credentials against the application's credential store. For most ASP.NET applications using Forms authentication, the credential store is usually a database.

With Basic and Digest authentication, however, there is no separate Web page to create, where the credentials need to be collected or validated. Instead, the user's credentials are collected by the user agent, and simply sent to the Web server in the Authorize header as part of the request. A standard HTTP authentication module, such as BasicAuthenticationModule from MADAM, is then responsible for parsing the credential information and validating the credentials. Rather than require this validation logic to be hard-coded in its authentication modules, MADAM delegates the validation piece to a user security authority. A user security authority is any class that implements the IUserSecurityAuthority interface from MADAM, which defines two members:

  • Realm—A string property that indicates the realm for authentication, the value of which is eventually used for the WWW-Authenticate header and displayed to the user by browsers (refer back to Figure 1).
  • Authenticate—A method that takes in a username, password, and other input parameters, and is tasked with validating the credentials.

MADAM comes with two IUserSecurityAuthority implementations:

  • FormsUserSecurityAuthority—Authenticates against the credentials explicitly added to the Forms authentication <credentials> element in Web.config. This is supplied primarily for testing purposes; in a production setting, you'd likely never store user credentials in Web.config.
  • DatabaseUserSecurityAuthority—Authenticates against a database credential store. Simply provide the connection string, connection type, and other information pertinent to the query, in order to get the user's password for authentication.

If needed, you can create a custom user security authority implementation to authenticate against your application's custom credential store. The demo Web application included in this article's download includes such a custom implementation that authenticates against ASP.NET 2.0's Membership system (see the MembershipUserSecurityAuthority class in the Web application's App_Code folder).

The IUserSecurityAuthority implementation used is specified in the Web.config file. The following example shows configuring MADAM to use the DatabaseUserSecurityAuthority class to authenticate against a custom database credential store.

<userSecurityAuthority 
    realm="MADAM" 
    provider="Madam.DatabaseUserSecurityAuthority, Madam" 
    connectionString="connectionString"
    connectionType="System.Data.OleDb.OleDbConnection"
    query="SELECT Password FROM Users WHERE UserName=@UserName"
    userParameter="@UserName"
    queryIsProc="false"
    exposeClearTextPasswords="false" />

The IUserSecurityAuthority interface does not provide any methods for retrieving passwords from the credential store. Rather, the Authenticate method simply receives the credentials and returns null if they are invalid. This approach works well for simple authentication schemes such as Basic, where the logic for validating the credentials is straightforward.

More complex authentication schemes may require greater effort in authenticating the received credentials. With Digest authentication, for example, the credentials received are a hash of many fields, including the username, realm, password, nonce, and so on. Furthermore, the structure of the hashed content can depend upon various choices made by the Digest implementation. Rather than trying to encompass all of these cases within the user security authority implementation, it may be best to let the authentication module determine whether the credentials are valid. To accomplish this, however, the authentication module needs to be able to access the user's credentials from the user security authority implementation.

As mentioned a moment ago, the IUserSecurityAuthority interface does not include any members for retrieving a user's password. Those user security authorities that want to support password retrieval must also implement the IUserPasswordProvider interface. Such user security authority implementations can dictate whether they'll return passwords in the hashed form needed by the Digest authentication scheme, in plain-text, or in both formats. Authentication modules then can use the GetPassword(username, passwordFormat) method from the IUserPasswordProvider interface to get at a user's password.

Figures 7 and 8 show the workflow when a user's credentials are sent in an Authorize header for Basic and Digest authentication, respectively.

Aa479391.madam07(en-us,MSDN.10).gif

Figure 7. User's credentials sent in an Authorize header for Basic authentication

Aa479391.madam08(en-us,MSDN.10).gif

Figure 8. User's credentials sent in an Authorize header for Digest authentication

Note that by implementing IUserPasswordProvider, a user security authority implementation is not obliged to give out passwords in clear-text if that's against its policy. In MADAM, all user security authority implementations allow you to specify, by means of the exposeClearTextPassword attribute in the configuration section, whether they should return clear-text passwords. If the attribute reads "0" or "false", public operations requesting a user's password in clear-text will result in the PasswordFormatNotSupportedException being thrown.

Bringing Digest Authentication Support into MADAM

Although it includes support for Basic authentication with BasicAuthenticationModule, MADAM was designed more as a framework for plugging in a standard HTTP authentication scheme in place of Forms authentication. Rather than limiting developers to selecting among the authentication schemes provided, MADAM is designed to allow developers to easily plug in existing authentication scheme implementations. For example, Greg Reinacker published a simple implementation of HTTP Digest authentication that consists of a single class named DigestAuthenticationModule. This class both issues the Digest challenge, and validates the credentials sent in the response. This implementation was published with his blog entry titled, "Web Services Security - HTTP Digest Authentication without Active Directory."

DigestAuthenticationModule has a virtual method named GetPasswordAndRoles(HttpApplication, userName, password, roles) that accepts the current HttpApplication instance servicing the request, along with the username sent in the credentials by the user agent. The password and roles parameters are out parameters, and they are used to obtain the specified user's password and roles. The method returns a Boolean value indicating whether the user was found in the credential store.

Greg's implementation relies on an XML file as the credentials store, but it can be integrated into MADAM by extending the DigestAuthenticationModule class and overriding its GetPasswordAndRoles method to use the configured user security authority implementation instead. The following code illustrates how to extend Greg's implementation for integration with MADAM. Specifically, the overridden implementation retrieves the plain-text password from the configured user security authority implementation, which is then used by the base class to determine whether the credentials are valid.

internal class RassocDigestAuthenticationModule : Rassoc.Samples.DigestAuthenticationModule
{
    protected override bool GetPasswordAndRoles(HttpApplication app, string username, out string password, out string[] roles)
    {
        roles = null;
        IUserPasswordProvider passwordProvider = (IUserPasswordProvider) UserSecurityAuthority.FromConfig();
        password = (string) passwordProvider.GetPassword(username, PasswordFormat.ClearText, null);
        return password != null;
    }
}
Note   Greg's code was designed to illustrate how you could get started with implementing your own Digest scheme implementation in ASP.NET, but bear in mind that a complete and thorough Digest implementation can be complicated to implement and get right from the ground up. If you're looking to use Digest authentication in production, we highly recommended that you first shop around for a commercial implementation. There are two commercial implementations listed in the Control Gallery at www.asp.net at the time of this writing: Visionalyse XHTTP Authenticator.NET and Kabel.NET HTTP Digest Authentication Module

Installing and Configuring MADAM

Adding MADAM to an existing ASP.NET Web application is straightforward—simply copy the MADAM assembly to your application's /bin directory, and add the appropriate Web.config settings.

Once the assemblies have been moved to the /bin directory, adjust your Web.config file so that it looks like the following.

<configuration>
    <configSections>
        <sectionGroup name="madam">
            <section name="userSecurityAuthority" type="System.Configuration.SingleTagSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
            <section name="formsAuthenticationDisposition" type="Madam.FormsAuthenticationDispositionSectionHandler, Madam" />
        </sectionGroup>
    </configSections>

    ...

    <madam>
        <userSecurityAuthority ... />

        <formsAuthenticationDisposition>
            <discriminators all="[true|false]">
                ...
            </discriminators>
        </formsAuthenticationDisposition>
    </madam>

    ...

    <system.web>
        <httpModules>
            <add name="FormsAuthenticationDisposition" type="Madam.FormsAuthenticationDispositionModule, Madam" />
            <add name="AuthenticationModule" type="MADAM Authentication Module Type" />
    </system.web>
</configuration>

In the <madam> element, specify the IUserSecurityAuthority implementation to use (and any custom settings), as well as the discriminators. In the <system.web> section, register FormsAuthenticationDispositionModule first, and then the authentication module you want MADAM to use (such as BasicAuthenticationModule for Basic authentication).

The demo Web application included in this article's download has different combinations of user security authority implementations presented as comments in the Web.config file. Consult the README file in the download for more information on setting up and configuring the demo application.

Disable Authentication Schemes in IIS

Recall that a Web server may implement multiple authentication schemes, sending the client a WWW-Authenticate header for each supported scheme, and that the user agent will usually select the most secure authentication scheme it supports from those available. For example, Internet Explorer versions that support Integrated Windows Authentication will choose that over Basic or Digest, if a challenge is received from all three. If IIS is set up to support authentication schemes, it detects the 401 status being sent back to the client, and tacks on WWW-Authenticate headers for each scheme it supports.

Taken together, these behaviors can lead to problems if IIS is configured to support Windows Integrated Authentication (which is IIS's default authentication model). As MADAM and the configured authentication module send back a 401 status with a WWW-Authenticate header, IIS adds on another WWW-Authenticate header for Windows Integrated Authentication. The user agent receiving both of these WWW-Authenticate headers will likely choose to send back an Authorize header, using the Windows Integrated Authentication protocol. IIS will then try to authenticate the user, and will deny access.

Similarly, if IIS is configured to support Basic or Digest authentication, it attempts to authenticate the supplied credentials against Active Directory before MADAM gets a chance.

To circumvent these problems, disable all authentication schemes in IIS for your Web site. To manage the authentication settings, go to the Internet Information Services Manager, and drill-down to your Web site or IIS application. Right-click the appropriate node, and select Properties. Click the Directory Security tab, and then click Edit. This will bring up the Authentication Methods dialog box (see Figure 9), which lists those authentication methods that are supported. Clear the check box for all authentication modes, leaving only Anonymous access selected. Click OK, and then click Apply to apply the changes.

Aa479391.madam09(en-us,MSDN.10).gif

Figure 9. Authentication Methods dialog box

Note that by leaving Anonymous access selected, you are not saying that your application does not require authentication, and that it is therefore open to public. What you are saying is that IIS should not perform any authentication checks. Once the request is past IIS, and it is delivered to your application, the ASP.NET modules for authentication and authorization provide the security.

Conclusion

Most ASP.NET Web applications that require authentication solely use Forms authentication. The Forms authentication workflow, however, is non-standard, cannot be easily performed by an automated process, and breaks clients that don't understand it. HTTP offers standardized authentication schemes, such as Basic and Digest, but these authentication schemes are typically implemented by a Web server such as IIS, where the ASP.NET developer has less flexibility over the credential store used. Furthermore, ASP.NET was designed to allow only one authentication scheme for each application, but there are scenarios where it may be desirable to vary the authentication scheme, based on criteria such as the requesting user agent or the requested URL.

MADAM helps overcome these limitations and allows for a single Web application to support multiple authentication schemes that validate against a common credential store. MADAM's core responsibility is to mute Forms authentication when a particular set of developer-defined conditions unfold. This task is handled by FormsAuthenticationDispositionModule, which replaces the 302 (Redirect) status with a 401 (Unauthorized) status, when needed.

After MADAM has muted the Forms authentication workflow, it is the responsibility of a properly configured and standard HTTP authentication module to send the challenge to the user agent, through the appropriate WWW-Authenticate HTTP header. Furthermore, when the user agent responds to this challenge, the authentication module must validate the supplied credentials. Validation is handled by a user security authority implementation. This article's download includes three sample user security authority implementations for validating against credentials in Web.config, a database credential store, or, for ASP.NET 2.0, through the Membership provider.

MADAM was designed as a flexible framework, with customization and extensibility in mind. The conditions by which MADAM mutes Forms authentication, the authentication module, and the user security authority implementation are all developer-defined parameters in Web.config. Commercial or custom-built authentication modules can be plugged into MADAM, and custom user security authority implementations can be created and utilized as well.

Happy Programming!

Special Thanks To

Before submitting this article to MSDN, we had a number of volunteers help proofread the article and provide feedback on its content, grammar, and direction. Primary contributors to the review process for this article include Eric Schönholzer, Hilton Giesenow, Raffael Zaghet, Chris Bottin and Carl Lambrecht.

References

 

About the Authors

Atif Aziz is a Principal Consultant at Skybow AG, where his primary focus is to help customers understand and build solutions on the .NET development platform. Atif contributes regularly to the Microsoft developer community, by giving talks at conferences and writing articles for technical publications. He is an INETA speaker and president of the largest Swiss .NET User Group (dotMUGS). He can be reached at atif.aziz@skybow.com, or from his Web site.

Scott Mitchell, author of six ASP/ASP.NET books and founder of 4GuysFromRolla.com, has been working with Microsoft Web technologies since 1998. Scott works as an independent consultant, trainer, and writer. He can be reached at mitchell@4guysfromrolla.com, or from his blog.

Show:
© 2014 Microsoft