restful.net

Having tried out several web development frameworks, and service frameworks while building restful services, I found that none of them were really suited for the job.

So I decided to build a very simple framework that is intended to make REST services and nothing else. Its not a RPC framework, its meant to be used for REST.

Let me give a very brief overview of why I thought the already established frameworks is not good enough.

MVC is simply too weird for my taste, first of all it uses more or less "automagic" mapping of methods in a controller to the verbs being used. I do not like that, I like to be in absolute control. Secondly you have to return an ActionResult instance from your methods that is wrong in my opinion and hides the real intent of the methods, i.e. it makes much more sense to return the objects that your method found. I think MVC is more meant to build websites and not web services or even REST services.

MVC's async implementation is laughable, seriously who thought up the silly way that you have to incment async operations, why not simply go with the standard BeginXX/EndXX methodology instead of making something really weird. I guess its because real async is kind of hard to wrap your head around.

I have also tried out both WCF and WCF HTTP, which is the next gen version of WCF that is tailored to build web services over http.

WCF and WCF HTTP is pretty good, first of all, its a service framework, its built with services in mind. Its very extensible, although it can be hard to find the exact place to extend if you want to change a particular behaviour. WCF supports asynchronous operations out of the box. You do not have to return a weird result object, but can return whatever you please, and object or void.

The only real reasons why WCF did not cut it with me, was of two simple reasons. You cannot build hiearchical rest services with WCF, i.e. you cannot have a /addressbook/{addressbookid} and let that be served by one class, and then have /addressbook/{addressbookid}/contacts be served by another class. All access to the same root must be served by the same service, which require you to have _ALL_ your methods in one service, which is bad. The other reason is that its not very easy to exchange the serializer of WCF, in fact its so hard, that I do not think the guys that made the framework ever wanted someone to exchange the serializers.

WCF HTTP comes with a nice feature where it looks at the Accept-Types header of the request and serves the correct content type, but if you start tweaking with your own serializers, i.e. lets say you do not like the JsonDataContractSerializer, like so many people does not, and inject your own, then you loose that functionality and have to build that as well.

I also briefly looked at the OpenRasta framework, which looks awesome and supports everything you would ever need, except it does not support asynchronous services, so you loose some scalability if you use that.

All that being said, I decided to build my own simple framework that tries to do all that I needed and its actually very simple to use.

It sill lacks a few features, not something you cannot built yourself into your service implementation but something that will come in time.

I have called my framework restful.net and you can find it at restful.net.

Restful.net supports the following features so far:

 

  • Automatic content type detection and serving of the requested content type
  • Supports asynchronous and synchronous api
  • Non intrusive, you can use any class as a REST service
  • Simple configuration, only add one http handler and configure the routes and you are good to go
  • You can return object instances from your services and the framework will handle serialization
  • Built in support for ETag / If-None-Match for proxy/browser caching capabilities
  • Plugs into an IOC container easily, so you can extend your REST services as you like

 

Features missing so far:

 

  • Authentication support natively
  • Logging support

 

The missing features is something  you can easily build into the REST service yourself by using interceptors or even just checking the auth headers in your methods, but it is something that should be part of the framework, so that kind of boiler plate code does not clutter your business logic.

To show how easy it is to build a REST service with the framework, I have implemted a Test REST service that is part of the code on codeplex.

Try it out and let me hear what you think :)

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 :)

Interfaces needed:
using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Security.Principal;

namespace Smithfamily.Blog.Samples
{
    /// <summary>
    /// An authentication and authorization provider for very simple applications
    /// Should probably be either implemented with a database backend, 
    /// or using a web.config custom section
    /// Implementors of this interface should provide a default no args constructor to be used
    /// by the AuthenticationModule
    /// </summary>
    public interface IAuthProvider : IDisposable
    {
        /// <summary>
        /// Validates the username and password and returns whether or not the combination is a valid user
        /// </summary>
        /// <param name="userName">The username to validate</param>
        /// <param name="password">The password to match</param>
        /// <param name="user">The user object created</param>
        /// <returns>true if the combination is a valid user;false otherwise</returns>
        bool IsValidUser(string userName, string password, out IBasicUser user);

        /// <summary>
        /// Determines whether or not the current request is allowed to continue for the given user
        /// </summary>
        /// <param name="request">The request to check</param>
        /// <param name="user">The user</param>
        /// <returns>true if request is authorized;false otherwise</returns>
        bool IsRequestAllowed(HttpRequest request, IBasicUser user);

    }

    /// <summary>
    /// interface for a very simple user object that contains the bare 
    /// minimum to do authentication against a real backend
    /// </summary>
    public interface IBasicUser : IIdentity
    {
        /// <summary>
        /// Gets or sets the username of the user.
        /// </summary>
        /// <value>The username of the user.</value>
        string UserName
        {
            get;
            set;
        }
        /// <summary>
        /// Gets or sets the password.
        /// </summary>
        /// <value>The password.</value>
        string Password
        {
            get;
            set;
        }
    }
}


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.

Basic Authentication Module:

using System;

using System.Collections.Generic;

using System.Text;

using System.Web;

using System.Security.Principal;

using System.Configuration;

using System.Reflection;

 

namespace Smithfamily.Blog.Samples

{

    /// <summary>

    /// HttpModule that provides Basic authentication for asp.net applications

    /// </summary>

    public class BasicAuthenticationModule : IHttpModule

    {

        private static IAuthProvider authProvider;

 

        #region IHttpModule Members

 

        /// <summary>

        /// Initializes the <see cref="BasicAuthenticationModule"/> class.

        /// Instantiates the IAuthProvider configured in the web.config

        /// </summary>

        static BasicAuthenticationModule()

        {

            string provider =

                ConfigurationManager.AppSettings["Smithfamily.Blog.Samples.BasicAuthenticationModule.AuthProvider"];

            Type providerType = Type.GetType(provider, true);

            authProvider = Activator.CreateInstance(providerType, false) as IAuthProvider;

        }

 

        /// <summary>

        /// Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule"/>.

        /// </summary>

        public void Dispose()

        {

            authProvider.Dispose();

            authProvider = null;

        }

 

        /// <summary>

        /// Initializes a module and prepares it to handle requests.

        /// </summary>

        /// <param name="context">An <see cref="T:System.Web.HttpApplication"/> that provides access to the methods, properties, and events common to all application objects within an ASP.NET application</param>

        public void Init(HttpApplication context)

        {

            context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);

            context.AuthorizeRequest += new EventHandler(context_AuthorizeRequest);

            context.BeginRequest += new EventHandler(context_BeginRequest);

 

        }

 

        /// <summary>

        /// Handles the BeginRequest event of the context control.

        /// </summary>

        /// <param name="sender">The source of the event.</param>

        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>

        void context_BeginRequest(object sender, EventArgs e)

        {

 

            HttpApplication context = sender as HttpApplication;

            if (context.User == null)

            {

                if (!TryAuthenticate(context))

                {

                    SendAuthHeader(context);

                    return;

                }

 

            }

            BasicUser bu = context.User.Identity as BasicUser;

            context.Response.Write(string.Format("Welcome {0} with the password:{1}", bu.UserName, bu.Password));

        }

 

        /// <summary>

        /// Sends the Unauthorized header to the user, telling the user to provide a valid username and password

        /// </summary>

        /// <param name="context">The context.</param>

        private void SendAuthHeader(HttpApplication context)

        {

            context.Response.Clear();

            context.Response.StatusCode = 401;

            context.Response.StatusDescription = "Unauthorized";

            context.Response.AddHeader("WWW-Authenticate", "Basic realm=\"Secure Area\"");

            context.Response.Write("401 baby, please authenticate");

            context.Response.End();

        }

 

 

        /// <summary>

        /// Handles the AuthorizeRequest event of the context control.

        /// </summary>

        /// <param name="sender">The source of the event.</param>

        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>

        void context_AuthorizeRequest(object sender, EventArgs e)

        {

            HttpApplication context = sender as HttpApplication;

 

            BasicUser bu = context.User.Identity as BasicUser;

            if (!authProvider.IsRequestAllowed(context.Request, bu))

            {

                SendNotAuthorized(context);

            }

        }

        /// <summary>

        /// Sends the not authorized headers to the user

        /// </summary>

        /// <param name="context">The context.</param>

        private void SendNotAuthorized(HttpApplication context)

        {

            context.Response.Clear();

            context.Response.StatusCode = 403;

            context.Response.StatusDescription = "Forbidden";

            context.Response.Write("403 baby, You are not allowed to see this");

            context.Response.End();

        }

 

        /// <summary>

        /// Tries to authenticate the user

        /// </summary>

        /// <param name="context">The context.</param>

        /// <returns></returns>

        private bool TryAuthenticate(HttpApplication context)

        {

            string authHeader = context.Request.Headers["Authorization"];

            if (!string.IsNullOrEmpty(authHeader))

            {

                if (authHeader.StartsWith("basic ", StringComparison.InvariantCultureIgnoreCase))

                {

 

                    string userNameAndPassword = Encoding.Default.GetString(

                        Convert.FromBase64String(authHeader.Substring(6)));

                    string[] parts = userNameAndPassword.Split(':');

                    IBasicUser bu = null;

                    if (authProvider.IsValidUser(parts[0], parts[1], out bu))

                    {

                        context.Context.User = new GenericPrincipal(bu, new string[] { });

                        if (!authProvider.IsRequestAllowed(context.Request, bu))

                        {

                            SendNotAuthorized(context);

                            return false;

                        }

                        return true;

                    }

 

                }

 

            }

            return false;

        }

 

        /// <summary>

        /// Handles the AuthenticateRequest event of the context control.

        /// </summary>

        /// <param name="sender">The source of the event.</param>

        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>

        void context_AuthenticateRequest(object sender, EventArgs e)

        {

            HttpApplication context = sender as HttpApplication;

            TryAuthenticate(context);

 

        }

 

        #endregion

    }

 

    /// <summary>

    /// Sample IAuthProvider that will authenticate all users, and only allow access to user with a username of bjorn

    /// </summary>

    public class BasicAuthProvider : IAuthProvider

    {

 

        #region IAuthProvider Members

 

        /// <summary>

        /// Validates the username and password and returns whether or not the combination is a valid user

        /// </summary>

        /// <param name="userName">The username to validate</param>

        /// <param name="password">The password to match</param>

        /// <param name="user">The user object created</param>

        /// <returns>

        /// true if the combination is a valid user;false otherwise

        /// </returns>

        public bool IsValidUser(string userName, string password, out IBasicUser user)

        {

            user = new BasicUser();

            user.UserName = userName;

            user.Password = password;

 

            return true;

        }

 

        /// <summary>

        /// Determines whether or not the current request is allowed to continue for the given user

        /// </summary>

        /// <param name="request">The request to check</param>

        /// <param name="user">The user</param>

        /// <returns>

        /// true if request is authorized;false otherwise

        /// </returns>

        public bool IsRequestAllowed(HttpRequest request, IBasicUser user)

        {

            return user.UserName == "bjorn";

        }

 

 

        /// <summary>

        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.

        /// </summary>

        public void Dispose()

        {

            //This is intentional, since we don't have any resources to free in this very simple sample IAuthProvider

        }

 

        #endregion

    }

 

    public class BasicUser : IBasicUser

    {

        /// <summary>

        /// Gets or sets the username of the user

        /// </summary>

        /// <value>The username of the user.</value>

        public string UserName

        {

            get;

            set;

        }

        /// <summary>

        /// Gets or sets the password.

        /// </summary>

        /// <value>The password.</value>

        public string Password

        {

            get;

            set;

        }

 

        #region IIdentity Members

 

        /// <summary>

        /// Gets the type of authentication used.

        /// </summary>

        /// <value></value>

        /// <returns>

        /// The type of authentication used to identify the user.

        /// </returns>

        public string AuthenticationType

        {

            get

            {

                return "Custom";

            }

        }

 

        /// <summary>

        /// Gets a value that indicates whether the user has been authenticated.

        /// </summary>

        /// <value></value>

        /// <returns>true if the user was authenticated; otherwise, false.

        /// </returns>

        public bool IsAuthenticated

        {

            get

            {

                return UserName != null;

            }

        }

 

        /// <summary>

        /// Gets the name of the current user.

        /// </summary>

        /// <value></value>

        /// <returns>

        /// The name of the user on whose behalf the code is running.

        /// </returns>

        public string Name

        {

            get

            {

                return UserName;

            }

        }

 

        #endregion

    }

}


Let me go throught the code in the BasicAuthenticationModule step by step so you understand what is happening.

Initialization:
/// <summary>
/// Initializes the <see cref="BasicAuthenticationModule"/> class.
/// Instantiates the IAuthProvider configured in the web.config
/// </summary>
static BasicAuthenticationModule()
{
    string provider = 
ConfigurationManager.AppSettings["Smithfamily.Blog.Samples.BasicAuthenticationModule.AuthProvider"]; Type providerType = Type.GetType(provider, true); authProvider = Activator.CreateInstance(providerType, false) as IAuthProvider; }


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.
i.e.

Web.config configuration of BasicAuthenticationModule:
<appSettings>
    <add key="Smithfamily.Blog.Samples.BasicAuthenticationModule.AuthProvider" 
value="Smithfamily.Blog.Samples.BasicAuthProvider"/> </appSettings>



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.

Dispose:
/// <summary>
/// Disposes of the resources (other than memory) used by the module that 
/// implements <see cref="T:System.Web.IHttpModule"/>.
/// </summary> public void Dispose() { authProvider.Dispose(); authProvider = null; }


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

Init:
/// <summary>
/// Initializes a module and prepares it to handle requests.
/// </summary>
/// <param name="context">An <see cref="T:System.Web.HttpApplication"/> that provides access to the methods, 
/// properties, and events common to all application objects within an ASP.NET application</param>
public void Init(HttpApplication context) { context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest); context.AuthorizeRequest += new EventHandler(context_AuthorizeRequest); context.BeginRequest += new EventHandler(context_BeginRequest); }


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 :)

context_BeginRequest:
/// <summary>
/// Handles the BeginRequest event of the context control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> 
/// instance containing the event data.</param>
void context_BeginRequest(object sender, EventArgs e) { HttpApplication context = sender as HttpApplication; if (context.User == null) { if (!TryAuthenticate(context)) { SendAuthHeader(context); return; } } BasicUser bu = context.User.Identity as BasicUser; context.Response.Write(
string
.Format("Welcome {0} with the password:{1}", bu.UserName, bu.Password)); }


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.

SendAuthHeader:
/// <summary>
/// Sends the Unauthorized header to the user, telling the user to provide a valid username and password
/// </summary>
/// <param name="context">The context.</param>
private void SendAuthHeader(HttpApplication context)
{
    context.Response.Clear();
    context.Response.StatusCode = 401;
    context.Response.StatusDescription = "Unauthorized";
    context.Response.AddHeader("WWW-Authenticate", "Basic realm=\"Secure Area\"");
    context.Response.Write("401 baby, please authenticate");
    context.Response.End();
}


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.

context_AuthorizeRequest:
/// <summary>
/// Handles the AuthorizeRequest event of the context control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/>
/// instance containing the event data.</param>
void context_AuthorizeRequest(object sender, EventArgs e) { HttpApplication context = sender as HttpApplication; BasicUser bu = context.User.Identity as BasicUser; if (!authProvider.IsRequestAllowed(context.Request, bu)) { SendNotAuthorized(context); } }


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.

SendNotAuthorized:
/// <summary>
/// Sends the not authorized headers to the user
/// </summary>
/// <param name="context">The context.</param>
private void SendNotAuthorized(HttpApplication context)
{
    context.Response.Clear();
    context.Response.StatusCode = 403;
    context.Response.StatusDescription = "Forbidden";
    context.Response.Write("403 baby, You are not allowed to see this");
    context.Response.End();
}


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.

TryAuthenticate:
/// <summary>
/// Tries to authenticate the user
/// </summary>
/// <param name="context">The context.</param>
/// <returns></returns>
private bool TryAuthenticate(HttpApplication context)
{
    string authHeader = context.Request.Headers["Authorization"];
    if (!string.IsNullOrEmpty(authHeader))
    {
        if (authHeader.StartsWith("basic ", StringComparison.InvariantCultureIgnoreCase))
        {

            string userNameAndPassword = Encoding.Default.GetString(
Convert.FromBase64String(authHeader.Substring(6))); string[] parts = userNameAndPassword.Split(':'); IBasicUser bu = null; if (authProvider.IsValidUser(parts[0], parts[1], out bu)) { context.Context.User = new GenericPrincipal(bu, new string[] { }); if (!authProvider.IsRequestAllowed(context.Request, bu)) { SendNotAuthorized(context); return false; } return true; } } } return false; }


The method below simply tries to authenticate the user on each authentication event, simple as that, and by using the method TryAuthenticate.

context_AuthenticateRequest:
/// <summary>
/// Handles the AuthenticateRequest event of the context control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> 
/// instance containing the event data.</param>
void context_AuthenticateRequest(object sender, EventArgs e) { HttpApplication context = sender as HttpApplication; TryAuthenticate(context); }


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
Adding module to web.config:
<httpModules>
  <add name="BasicAuthenticationModule" 
type="Smithfamily.Blog.Samples.BasicAuthenticationModule"/> </httpModules>


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

 

Web.config configuration of BasicAuthenticationModule:
<appSettings>
<add key="Smithfamily.Blog.Samples.BasicAuthenticationModule.AuthProvider"
value="<enter fully qualified name for your implementation of IAuthProvider"/>
</appSettings>



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

 

Turn on authentication in Web.config:
 <system.web>
      <authentication mode="None" />
      <authorization>
        <deny users="?" />
      </authorization>



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:

Accessing the current user in asp.net markup:
<%IBasicUser user = User.Identity as IBasicUser;
Response.Write(user.UserName)%>



Please let me know if this example dosen't work for you and I will try to help you make it work :)

ASP.NET http authentication header rewrites

I were recently a victim of asp.net's browser detection and on the fly content change.

The story is:

I work for a company called ZYB, and for a new feature we are developing we have created some HttpModules, which basically provides means of authenticating our users against their usual ZYB account using either Bacic Authentication or Digest Authentication.

This is all fine, and since both Basic Authentication and Digest Authentication are pretty straightforward to implement we were done in a few days.

We tested the modules, simply by invoking the urls that were protected by the web.config sections:

<authorization>
<deny users="?" />
</authorization>
<authentication mode="None" />

Please note that when developing your own authentication modules, you have to allow Anonymous users in IIS, and remove all other authentication options, and in the web.config, you have to set the

<authentication mode="None" />

Otherwise your modules wont kick in.

But back on track, this is not the reason why I am writing this blog post, its simply because we suddenly discovered that for some user agents, asp.net was modifying the HTTP 401 Unauthorized status code to 200 OK, but still sent the WWW-Authenticate header anyway.

This does seem like a bug, and I traveled many paths before finding out that it was the user-agent header that triggered the Status rewrite.

I am pretty sure asp.net should never rewrite the status codes, at least not for authentication, since it pretty much screws up authentication.

Since changing the User-Agent was not an option, I tried to change the Request headers, but microsoft have made that impossible with a recent fix to .NET.

So I was getting desperate, and I tried to use some of the .browser files you can place in the App_Browsers directory, but I could not find any options for "Do not fuck up my status http headers", so no luck there.

In a desperate measure, I looked at the clientTarget xml elements you can put into the web.config, but no luck there as well, so my last try before rewriting the entire feature, to use in place authentication within the http body, was the

browserCaps section in system.web in the web.config.

I found that if I added the following section:

    <browserCaps>
      <result type="System.Web.HttpBrowserCapabilities"/>
      <use var="HTTP_USER_AGENT"/>
    </browserCaps>

asp.net stopped fucking up my headers, and returned the proper 401 status code.

The user agent that fucked everything up, was more or less any mobile device out there, and ZYB being a mobile company, its damn hard to just ignore that.

So this blog post is hopefully a help for people stuck in the same situation.