Implementing Basic Authentication in ASP.NET 2.0
I have many times wanted to implement basic authentication in asp.net applications, but has been unwilling to use the built in basic authentication of IIS, since I think its a bother to use either the Windows machine's users since its a pain to administer and does not easily tie into an existing solution of user authentication you have, like a CMS or what have you. People would proably argue that I should use forms based authentication, but thats not always possible, like if you want to have your services accessible from lets say a program that runs on another machine, i.e. a service that polls for data. That program need Basic authentication or Digest authentication, since thats much more easy to implement when you dont have a browser as the client.
You could use the built in WindowsAuthentication http module of asp.net, but then you are stuck with using the windows users, and manage roles for them in windows as well and that kind of sucks, since you would proably want to use your application's user administration to manage access to your application.
So what you want to do is create your own HttpModule that provides Basic Authentication functionality.
It sounds harder than it actually is, and I have created a complete package of files you can copy/paste and implement a little code yourself, and then you have a ready to plugin Basic authentication module.
What I have created is a HttpModule that takes care of the Basic Authentication, then I have created a couple of interfaces that needs implementing. The implementations of the interfaces will provide answers to the Basic Authentication HttpModule about whether or not a given user is a valid user, and whether or not the user is allowed to see a given page or do a given request.
The code for the interfaces is pretty simple, which interfaces usually is since its the implementation that does all the work :)
The IAuthProvider is the interface for the class you need to implement that will do lookup in your backend for users, and will validate whether or not a user have access to a give resource. The IBasicUser is an interface for a very simple user object that can contain the bare minimum to authenticate a user. I have implemented IBasicUser and will pass an implementation of that to the configured IAuthProvider.
I have also made a silly implementation of the IAuthProvider that will accept any users for logon, but will only authorize a user with the username bjorn, i.e. anyone can log on, but only I am allowed to do anything. The implementation is just as an example, please don't implement your versions like this, but rather read the users from a database, and validate their password properly.
The HttpModule itself is pretty simple as well.
Let me go throught the code in the BasicAuthenticationModule step by step so you understand what is happening.
These lines of codes configures the authentication module, and is done only once per application restart.
What these lines do is that they look in the Web.config for a appSettings parameter called Smithfamily.Blog.Samples.BasicAuthenticationModule.AuthProvider, and tries to create an instance of the fully qualified type name and use it as its implementation of IAuthProvider. This web.config parameter is where you configure the basic authentication module to use your IAuthProvider implementation.
These lines of codes simply calls Dispose on the implementation of the IAuthProvider just in case there is some resources that need to be disposed.
The following lines of codes is simply telling the application that the module wants to be part of the following events:
AuthenticateRequest, AuthorizeRequest and BeginRequest.
The reason why we need hooks on these events is that this is where we will do our magic, so without registering event handlers on these events there will be no basic authentication
The following event handler gets called each time a request begins on the server, and this is the perfect place to try to authenticate the user, which is what we do.
We call the method TryAuthenticate and if we get a false back from that method, we send the needed authentication headers to the user and returns.
If we the TryAuthenticate method returns true, then we just let everything flow, but injects some silly text on top of the page.
The last two lines of the method you need to remove when using this module, otherwise all pages will contain the text:
Welcome user with the password: xxxx, which is kind of not cool :)
The method SendAuthHeader as shown below simply sends the required headers to the browser, making it prompt the user for a user name and password. When using this code change the line where the WWW-Authenticate header is added and change the "Secure Area" to what you want, i.e. your application name.
The login box for the above code will look something like the one below if you are using firefox.
The method below handles the authorization part, i.e. checking whether or not the user is allowed to do what he is trying to do. At this point the user is already logged on, and we know that the user is a valid user, so all we do is grapping the user from the HttpApplication and asking the implementation of the IAuthProvider whether or not the user is allowed to do this request.
If the user is not allowed to do what he is trying to do, we send a http response back indicating that the user have no permissions to do what he is doing.
If the user is allowed, then we just let things flow.
The method below simply sends the correct headers, telling the browser that the user is not allowed to do what he is trying, and therefore the browser should not retry.
The method TryAuthenticate is where the "magic" happens, this is where the basic authentication part is checked, and then provided the user actually sent a username and password, we ask the IAuthProvider whether or not the user is a valid user.
If we get a go from the IAuthProvider that the username and pasword is a valid combination, then we inject an implementation of the IBasicUser into the HttpApplication for further use in the application. Then we proceed to ask whether or not the user is allowed to do what he is doing, and if everything checks out okay we return true, otherwise we return false.
Please note that the IBasicUser that we put into HttpApplication can be accessed from any asp.net pages, just by accessing the Page's property called User, so its a neat way to inject information about the current user into the standard objects of asp.net.
The method below simply tries to authenticate the user on each authentication event, simple as that, and by using the method TryAuthenticate.
See, that wasen't so hard, so what you need to do to make this work for is simply:
- Implement IAuthProvider using your own database of users, your own xml structure or what ever means of authenticating and authorizing the users.
- Add the Module to your application by editing the web.config and putting the following lines into the web.config
Naturally there will be other modules present, just inject the module line as the last element in the <httpModules> collection.
- Configure the module by adding the following lines to the web.config. Please remember to add the full name, i.e. Your.NameSpace.YourClassName
That should be in, just allow anonymous access in the IIS configuration and remove all other authentication options in the IIS, and you should be set.
I have seen cases where it still dosen't work, and if thats the case, try adding the following items to the Web.config.
- Turn on the authentication module by adding the <authentication> element with the mode None, its strange, but it has to be none, otherwise it will use one of the built in, which kind of defeats the purpose of this module :)
- Put in the <authorization> element and say that all anonymous users is not allowed
I hope this gave you an insight in how you can implement basic authentication pretty easily, and remember you can implement your own IBasicUser, and put all other kinds of stuff in there like items from your application, and then you have access to everything by calling the Page.User property, like:
Please let me know if this example dosen't work for you and I will try to help you make it work :)