.NET HTTP Module – sample code
Introduction
Digest authentication has been around for quite some time, but has stayed in obscurity to some extent. This is probably due to the fact that a limited number of servers support it, and a limited number of clients support it as well. IIS also requires certain Active Directory settings to be made in order to use the built-in implementation. However, it overcomes many of the weaknesses of Basic authentication. In particular, it does not require an encrypted channel for communications, because passwords are not sent in clear text (as they are in Basic). The benefits definitely outweigh the costs, as evidenced by Microsoft choosing to use Digest for their MapPoint.NET service.
In a previous article, I discussed the merits of leveraging HTTP authentication mechanisms for web services, rather than rolling your own scheme with custom SOAP headers. Later, I posted a sample which demonstrated how to implement Basic authentication in .NET without having to authenticate against Active Directory, and without using a 3rd party ISAPI filter. In this article, I will present an interoperable implementation of Digest authentication, also implemented in .NET managed code, without the use of the built-in IIS implementation and Active Directory. As with the Basic sample, this code will run even on a shared server. Note that both samples can run simultaneously on the same server, and your site can then support both Basic and Digest.
A link to download the code is at the end of this article.
If you find this post useful, please support this site and go buy yourself something on Amazon.com!
How does Digest Authentication work?
Basically, the client starts by making an un-authenticated request to the server, and the server responds with a 401 response indicating that it supports Digest authentication. The server also sends a nonce, which can be thought of as an opaque token. The client then re-requests the resource, sending up the username, and a cryptographic hash of the password combined with the nonce value. The server then generates the hash itself, and if it matches the request’s hash, the request is allowed.
Let’s explore this in a bit more detail. For all of the gory details, refer to the Digest specification.
The client first makes a request, looking like the following (some parts removed for clarity):
GET /FooService/basiconly/Service1.asmx HTTP/1.1
Accept: */*
Host: localhost:8100
Connection: Keep-Alive
The server responds with a challenge:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest realm=”RassocDigestSample”, nonce=”Ny8yLzIwMDIgMzoyNjoyNCBQTQ”, opaque=”0000000000000000″, stale=false, algorithm=MD5, qop=”auth”
Let’s look at the WWW-Authenticate line in more detail:
- Digest – specifies that the server supports Digest authentication
- realm=”RassocDigestSample” – specifies the authentication realm. This is intended to give the client some idea of which credentials are being requested.
- nonce=”Ny8yLzIwMDIgMzoyNjoyNCBQTQ” – specifies the nonce value the client should use for an authenticated request.
- opaque=”0000000000000000″ – an opaque value that the server needs the client to pass back to it unchanged. Not used in this sample implementation.
- stale=false – indicates that the previous request was not denied because of a “stale” nonce. If this parameter was true, it would mean that the request looked ok, and the credentials were correct, but the nonce was invalid.
- algorithm=MD5 – specifies the hash algorithm to use when computing the digest.
- qop=”auth” – indicates “quality of protection”. “auth” means authentication only, and “auth-int” means authentication plus integrity protection. This sample implementation will only send “auth”.
The client will then send an authenticated request:
GET /FooService/basiconly/Service1.asmx HTTP/1.1
Accept: */*
Host: localhost:8100
Authorization: Digest username=”test”, realm=”RassocDigestSample”, qop=”auth”, algorithm=”MD5″, uri=”/FooService/basiconly/Service1.asmx”, nonce=”Ny8yLzIwMDIgMzoyNjoyNCBQTQ”, nc=00000001, cnonce=”c51b5139556f939768f770dab8e5277a”, opaque=”0000000000000000″, response=”afa30c6445a14e2817a423ca4a143792″
And looking at the Authorization header in more detail:
- Digest – indicates that this is a Digest authentication header.
- username=”test” – the user name.
- realm=”RassocDigestSample” – the authentication realm, specified in the WWW-Authenticate challenge.
- qop=”auth” – the requested quality of protection; should match the challenge.
- algorithm=”MD5″ – the hash algorithm used to calculate the digest. Should match the challenge.
- uri=”/FooService/basiconly/Service1.asmx” – specifies the requested URI on the server. It is repeated here to ensure interoperability through proxies.
- nonce=”Ny8yLzIwMDIgMzoyNjoyNCBQTQ” – the nonce value used for the request.
- nc=00000001 – indicates the number of requests the client has made using this particular nonce value. This information can be used by the server to protect against replay attacks; it is ignored in this sample.
- cnonce=”c51b5139556f939768f770dab8e5277a” – opaque value generated by the client.
- opaque=”0000000000000000″ – opaque value from the challenge.
- response=”afa30c6445a14e2817a423ca4a143792″ – the 32-character digest.
The digest is calculated based on the username, the password, the realm, the nonce, the nc value, the cnonce value, the qop value, and the uri value. For more details on the actual hash calculation, refer to the spec.
The important things to note about Digest authentication are the following:
- The password is never transmitted in clear-text, in contrast to Basic authentication.
- The server can optionally monitor and track the nc value, making it resistant to replay attacks (but at a price of having to track the necessary information).
- The server can carefully choose and restrict nonce values, such that a particular nonce is only valid for a certain time, only from a particular client, or only for a certain request. This again provides resistance to replay attacks.
Sample Implementation Details
The sample code is designed to be installation-compatible with the Basic authentication example, in that the configuration, etc. are very similar; for example, they share the same users.xml file.
In this sample, nonce values are chosen to provide a minimum level of replay attack protection, but it is certainly weak. The nonce itself is derived from the base64 encoding of the text representation of the nonce expiration time, which is 1 minute after the current server time. So, a nonce handed out by this server will be valid for 60 seconds from the issue time. If a client makes a request after the 60 seconds are up, a 401 response will be sent with a stale=true property in the WWW-Authenticate header.
Quality-of-protection is set to authenticate only; message integrity is not supported in this sample (although it would be very easy to add).
Installation Instructions
1. Build DigestAuthMod.dll, and copy it to your web application’s bin directory on your server.
2. Make the following changes to your web.config file (in the <system.web> section):
- Change authentication line to: <authentication mode=”None” />. We need to disable the built-in ASP.NET authentication.
- Add an authorization section if you wish, such as <authorization>
<deny users=”?” />
</authorization>If you use DigestAuthMod to authenticate, you can still leverage the built-in ASP.NET authorization capabilities.
- Add the following lines to wire the DigestAuthMod.dll into the ASP.NET pipeline. <httpModules>
<add name=”DigestAuthenticationModule”
type=”Rassoc.Samples.DigestAuthenticationModule,DigestAuthMod” />
</httpModules>
3. Make the following changes to your web.config file (in the <configuration> section), and edit appropriately:
<appSettings>
<add key=”Rassoc.Samples.DigestAuthenticationModule_Realm”
value=”RassocBasicSample” />
<add key=”Rassoc.Samples.DigestAuthenticationModule_UserFileVpath”
value=”~/users.xml” />
</appSettings>
4. Copy the sample users.xml file into your virtual directory.
The last thing you need to do is make sure all IIS authentication mechanisms (Basic, Integrated, and Digest) are turned off, and only anonymous is enabled. You can do this within the IIS Manager, or typically hosting providers will provide a way to make sure that Basic is turned off for your hosted sites/virtual directories.
Suggested Enhancements
The following is a list of suggested enhancements to this code, should you choose to use it in a production environment:
- Enhance the nonce-generation code. The potential replay vulnerability can be eliminated, at the cost of processing time on the server; however, it also limits the client’s ability to pipeline multiple requests. Weigh the cost and risk for your own application, and make the decision for yourself.
- Implement the message integrity checks. There’s really no good reason not to, and it’s cheap insurance against messages getting modified in transit.
- Don’t store clear-text passwords; instead, store the username and the associated digest.
- Change the credential store to a database, LDAP directory, or whatever you need for your application.
If you have any questions, please feel free to contact me at gregr@rassoc.com.
Greg Reinacker
update (6/9/02 6:43pm MST):
The sample code has been modified to fix the following bugs:
1. When using Opera, the cnonce value is a base-64 encoded value which may contain the ‘=’ character. The original parsing code did not correctly handle this situation.
2. Mozilla uses the entire URI (including the query string) for the uri field in the authorization header, whereas Internet Explorer does not. The original parsing code would not correctly handle the ‘=’ characters in the header.
Many thanks to Justin Rudd for bringing these problems to my attention!
update (8/29/02 8:55am MST):
If you’re using this code on a Windows XP machine that is a member of a domain, IIS by default has Digest authentication turned on, and that will conflict with the implementation here. Gordon Weakliem published a fix for this problem, partially reprinted here for completeness:
After some experimentation (and Greg’s suggestion), I determined that you can use the IIS ADSI interface to shut off Digest. Here’s a little bit of VBScript that does the trick on the root of the site:
' Select the virtual directory you want to modify, you'd need to determine the ' site number if you're running more than 1 site, but that's not supported on XP Pro. ' append a virtual directory name here to specify only a subdir RootNodePath = "IIS://LocalHost/w3svc/1/Root" Set oRootNode = GetObject(RootNodePath) If Err <> 0 Then Display "Couldn't find the path " & RootNodePath & "!" WScript.Quit (1) End If oRootNode.AuthFlags = 1 ' turn off all authentication except Anonymous oRootNode.SetInfoThe AuthFlags argument is a bitmask containing the authentication options for the given object, where 1 = Anonymous, 2 = Basic, 4 = NTLM and 16 = Digest. So this example sets the authentication to Anonymous. AFAICT, IIS doesn’t expose a separate property for Digest as it does for the others, so it appears that this is the only way to shut off digest.
update (3/1/03 7:10pm MST):
Updated the instructions above to fix the incorrect value in the appSettings section of the config file.
Hi,
Please post some sample code that details how a web service consumer accesses a web service that uses the digest authentication model.
Hi there! Great work, but I just wanted to let you know that your article has been ripped off, word-by-word (minus the credit that is) by this indian guy:
www. arunav. net/arunav/authentications/
Hi Murki,
The page you are refering to was not meant for public access, (only accessible by my ex students). The error has been rectified, my apologies.
And it is one of the seventeen (17) different custom authentication examples in .net that is maintained at my server from different sources (that I don’t remember).
If you download the sample application from my site you will find it is a web project demonstrating implementation for custom authentication taking digest authentication as an example.
For the credit, digest authentication is invented by Senthil Sengodan and Tat Chan (and I don’t see any credit for them anywhere).
Lastly, referring me as “indian guy”, perhaps there is something you intend to suggest. I rather like being referred as a ordinary programmer/blogger with no specification for my nationality, ethnicity, religion, gender, etc.
please do not devide the netizens.
Arunav, as for credit to the digest authentication authors, I did link to the RFC on the IETF site; however, what you found here on my site was original content describing one way to implement digest auth.
You, on the other hand, copied my article word-for-word, removed the link to my site, changed the contact information to your own, and passed the article off as your own. Whether it was for public use, or only for your students, is irrelevant; it was still a violation of US copyright laws. I see from your LinkedIn profile you reside in India; a perusal of US/India copyright treaties indicates to me this was also a violation of Indian copyright law.
Thank you for removing it.
Hi there. Great articles on both basic and digest authentication. I’ve had success implementing both. I have a question about logically implementing your suggested enhancements.
When you recommend storing the username and associated digest in the database instead of plain text passwords, are you referring to the HA1 hash in OnAuthenticateRequest in your code? If not, I don’t see how the hashedDigest or unhashedDigest can be stored when they’re both based on an expiring nonce. Alternatively, if I was to store the HA1 hash in the DB, that seems slightly insecure as it’s based on two known values: username and realm. It seems like the password could be brute forced since there’s no salting involved.
I’m okay with the level of security provided by storing the MD5 hash of HA1 with no salting. I’m more curious as to whether this is common practice. Right now I store my passwords as a MD5 hash made up of the password and a salt made up of a keyword and the date that the user first signed up. I know I’ll need to change my password storage scheme to work with this code (and digest authentication in general), I’m just having trouble wrapping my head around just how to do it. Any suggestions would be appreciated.
Bob, I think many implementations actually end up storing the password itself using a reversible encryption mechanism. No one would necessarily recommend this, but it is probably more common practice than you might think. As i understand it, the implementation of active directory actually worked this way when digest was enabled, as of the writing of the original article.
I’m trying to include some user credentials for accessing a remote
webservice. The remote location requires that I use http Basic
authentication(RFC2617), which means, from browsing around, I need to include
the user name and password in the HTTP header, but I’m not quite sure
how to access the HTTP header that is sent with the webservice soap
message request.
whenever i try to call webservice i get the error username is required.
can u plz send me some working code hot to call webservice giving username and password using Http Basic
authentication(RFC2617)
Can anyone help me plz?
i m using asp.net with c#.
Thanks in Advance.
Kunal Tilak
kunal, something like
myProxy.Credentials = new NetworkCredential(“user”,”password”);
Hi, I implemented the Digest version and it works great, and I modified the code so I use passwords in the file but encrypted.
But I have a tiny problem, not blocking me but a little bit annoying.
When I add my secured webservice to another application as webreference, it asks me the username and passord twice.
Once for the file webservice.asmx
Another for the file webservice.asmx/$metadata
Any idea why? Can it be avoid some way?
Thank you very much for the article. I was really worried about security in my webservices.
Another thing that I noticed. If the application pool is pipeline integrated, it doesn’t work. What is pipeline integrated useful for?
tried
To make it accept the module in integrated module. Don’t get the 500 error message anymore but it doesn’t ask my login and password. Instead it throws a “401 Refused Access” directly.
<system.webServer>
<validation validateIntegratedModeConfiguration=”false”/>
</system.webServer>
sigh, yeah, I see that DigestAuthenticationModule inherits from IHttpModule. Maybe we should inherit something else instead?
Great, thanks to a post in the BasicAuthenticationModule article, I get this to work.
First, name it CustomDigestAuthenticationModule in the web.config.
Second, to make it works with Integrated pipeline, add this section:
<system.webServer>
<validation validateIntegratedModeConfiguration=”false”/>
<modules>
<add name=”CustomeDigestAuthenticationModule” type=”Rassoc.Samples.DigestAuthenticationModule, DigestAuthMod”/>
</modules>
</system.webServer>
This way it will work as well with integrated pipeline as without it. If you only want to make it work in integrated pipeline mode, erase httpmodule section and add only
<system.webServer>
<modules>
<add name=”CustomeDigestAuthenticationModule” type=”Rassoc.Samples.DigestAuthenticationModule, DigestAuthMod”/>
</modules>
</system.webServer>
The section <validation validateIntegratedModeConfiguration=”false”/> is only there to avoid validation by IIS between the configuration and the integrated mode which doesn’t accept an httpModule section.
——————————————
# Chinh said:
August 24th, 2009 at 2:09 pm
For anyone trying to get this to work with IIS7, you need to do this:
– In web.config, change the name of the httpModule to “CustomBasicAuthenticationModule”. In IIS7, there’s already a built-in module named “BasicAuthenticationModule”.
– Follow the instructions here http://bdotnet.in/blogs/navaneeth/archive/2008/07/06/2056.aspx
Chinh
Hi,
I need a client making the authentication programmatically (not via browser).
So, create the 1st request and I catch the WebException.Response. Now I have access to the nonce and to all parameters. In order to authenticate, I need to send the authenticated request, according to the RFC (I mean I’ve to create the header using nonce, user, pass, MD5,..). Do you have any working code to create this response ?
thanks in advance.
Frank
Solved.
(I did not see the previous answer, Thanks to Greg):
Dim req3 As HttpWebRequest
req3 = CType(WebRequest.Create(uri), HttpWebRequest)
req3.Credentials = New NetworkCredential(“myuser”, “mypass”)
I love your module and have been using for some time now. But unfortunately at my work they want to migrate do WCF.
What a shock, the module didn’t work…WCF uses security separated from the IIS authentication system.
WCF accepts custom security but only if certificates or https is used. Apparently the credentials are sent in clear mode…
What a crap… I understand why, but leave the choice to the developers. Only they know their environments.
After a complete week of intensive research I found the solution thanks to this project
http://custombasicauth.codeplex.com/
I didn’t like it very much because it implies that you create a new authentication mode and the program is not the same if it is used for IIS 6 or IIS 7. But it lead me to the necessary changes so I can continue using your incredible module.
The magic is:
And in your Service class add the following above your class declaration
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class Service
So simple, and yet so hard to find out.
Magic part
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled=”true” />
</system.serviceModel>
and if you want to use Soap12 instead of Soap11 which is the default for BasicHttpBinding
but you will have to configure the service section too
Source
http://www.pvle.be/2008/10/soap-12-message-format-with-basichttpbinding/
I am not able to receive the authorization Header in the server side. I am getting the following error.
The HTTP request is unauthorized with client authentication scheme ‘Anonymous’. The authentication header received from the server was ‘Digest
I am scratching my head from last 2 weeks..
Sorry, forgot to tell that for my work around, it only works if you add the web service the old fashion. “Add Service Reference => Advanced… => Add Web Reference”.
But there is a little problem… It adds for each method parameter, an extra parameter.
That’s because the Wcf use DataContractSerializer instead of XmlSerializer. If you switch the serializer, it will give you the regular functions.