Basic Authentication is one of the authentication methods supported by any Web Server and by any browser. It should not be used in the clear, and normally its used in conjunction with a server certificate (SSL) to encrypt all information going in the wire.
When using Basic Authentication, the credentials you set on the first authentication dialog are sent on every single request until the TCP Session ends (the browser is closed). This is so you don't keep getting the dialog every time.
There are times, however, when you'd wish the credentials dialog was shown again by the browser after some period of idle time. You never know if the end user went to get a cup of coffee and left his workstation and his browser open, or you may have a very sensitive application that requires the user to validate himself every, say, 10 minutes.
While this can be acomplished with managed modules (a.k.a. HttpModules), it only affects ASP.NET apps, and your environment may have applications written in other languages, like ASP, PHP, etc. In these cases you want a solution that works for every page you find sensitive.
What I'm about to describe next assumes the environment uses Basic Authentication.
With this in mind, the requirements for such a solution are:
- Wait for the user to authenticate via Basic Auth
- Record the beginning of the TCP session after the user has authenticated
- On every request, record the last access date
- On every request, check if the idle time is above a certain limit. If it is, then ask the user credentials again. Since the 401 response will end the current TCP Session, we're certain to start a new TCP Session when the user sends its credentials again.
Based on the requirements above, using managed filters (a.k.a HttpFilters) is quite simple. The events we're interested in are:
- When the request first arrives at IIS (PreProcHeaders event)
- When the Basic Auth credentials are about to be validated by IIS (Authentication event)
- When the access is denied by IIS (AccessDenied event)
Assuming we have the sessions idle timeout defined somewhere (the configuration file for example), we're all set. Lets build the managed filter:
using System;
using System.Text;
using KodeIT.Web;
namespace MyFilter
{
public class Class1 : IHttpFilter
{
const string SESSION_LOGGEDIN = "LoggedIn";
public void Dispose() { }
public void Init(IFilterEvents events)
{
events.PreProcHeaders += new EventHandler(OnPreProcHeaders);
events.Authentication += new EventHandler(OnAuthentication);
events.AccessDenied += new EventHandler(OnAccessDenied);
}
void OnAccessDenied(object sender, AccessDeniedEventArgs e)
{
e.Context.Session.Remove(SESSION_LOGGEDIN);
}
void OnAuthentication(object sender, AuthenticationEventArgs e)
{
if (e.Context.ServerVariables[ServerVariable.AUTH_TYPE].ToLower().Equals("basic"))
{
e.Context.Session[SESSION_LOGGEDIN] = DateTime.Now;
}
}
void OnPreProcHeaders(object sender, PreProcHeadersEventArgs e)
{
if (e.Context.Session.ContainsKey(SESSION_LOGGEDIN))
{
if ((DateTime.Now - (DateTime)e.Context.Session[SESSION_LOGGEDIN]) > new TimeSpan(0, 0, 10))
{
string response = String.Format(
"HTTP/1.0 401 Unauthorised\r\nWWW-Authenticate: Basic\r\n\r\n");
e.Context.WriteClient(ASCIIEncoding.ASCII.GetBytes(response));
e.Context.TerminateRequest(false);
}
else
{
e.Context.Session[SESSION_LOGGEDIN] = DateTime.Now;
}
}
}
}
}
You might wonder about the e.Context.Session collection and its purpose. This collection is a property bag associated with the TCP Session. Whenever the a TCP Session is initiated (the first request from the browser arriving at IIS) the property bag is created. This property bag instance will be alive while the TCP Session is alive. When the TCP Session is terminated (handled by EndOfNetSession event) the session instance is also destroyed.
Going back to the code, the TimeSpan(0, 0, 10) you see here can be set on a configuration file. In this example its value is 10 seconds, which means that if you do not make a new http request in 10 seconds, you will be asked for your credentials again.