Monthly Archives: May 2012

Both Forms Authentication and Basic Authentication

The question was asked:

Given an asp.net application that in itself uses Forms Authentication, what is the best practice for securing a public-facing demo site such that nobody who is not in the “in crowd” can see the site at all?

My answer was pretty cool, and I wanted to remember it someday when I had a similar question.  Up-vote it if you too think it’s cool:

Typically the “demo sites” are secured with Basic Authentication.  e.g. return a 401 to the browser with a basic authentication challenge that it turns into prompting for credentials.  In theory, once this is done, the rest of the site is just regular stuff — forms auth when needed.

The difficulty with this approach in ASP.NET comes in the fact that the default FormsAuthenticationProvider is hard-wired to interpret a 401 as “need to 302 to the login page.”  With that as a premise, getting both Forms Authentication and Basic Authentication to happen simultaneously is a challenge.

Also, the Basic Authentication built-in to IIS uses Windows as the authentication store (Active Directory or local windows accounts.)  Getting it to use a different credential store is not easy to do “in the box”.

http://custombasicauth.codeplex.com/ is a project I’ve been watching that is quite intriguing.  It provides a custom Basic Authentication provider that allows you to rig up Basic Authentication from a different provider store.  Pop open the source to http://custombasicauth.codeplex.com/SourceControl/changeset/view/53965#183990 and http://custombasicauth.codeplex.com/SourceControl/changeset/view/53965#183995 and see that they’re just extracting the Base64-encoded header, and comparing it to an ASP.NET Membership Provider.  With that as a premise, you could rig up a similar HttpModule to compare the header data to a user/pass stored in AppSettings, and include the module in your demo site.  The magic sauce is that you don’t set the 401 status on Authenticate, you do so at EndRequest — after the FormsAuthenticationModule has finished it’s “401 to 302 to login page”.  The only down-side is the <location> tags have to be used by Forms Auth or by Basic Auth, but not both.  If the use-case is truly “secure the entire demo site”, then it’s sufficient to code the Basic Auth module to “just do it all”.  I’m about 2/3 of the way doing exactly this.  When I’m done, I’ll likely post it to GitHub as it’s turning out pretty cool.  Alas, the technique isn’t that hard, and perhaps the description of the solution is sufficient.

And if you really want a hands-off, no-code solution, install http://custombasicauth.codeplex.com/.  It even gives you pretty config windows in IIS.  :D 

Elmah: return the ErrorId from Signal.Raise()

ELMAH is an awesome error logging framework for ASP.NET.  It provides both an elegant way of capturing errors, and an elegant way of displaying them later.  It’s available as a NuGet package, and is both Web Forms and MVC friendly.

Generally, Elmah “just works” and you can walk away happy as a clam.  But there’s times where you want to do custom error logging.  The general solution is to do something like this:

ErrorSignal.FromContext(HttpContext.Current).Raise(ex, HttpContext.Current);

The up-side is you can log anything you want then gently recover.  The down-side is you can’t get the logged ErrorId.  Granted, based on your Elmah filters, maybe it wasn’t logged.  But if it was and that location stores an id (see Elmah’s SqlErrorLog) then it’d be really nice for Raise() to return that Id.

The problem is elegantly described http://code.google.com/p/elmah/issues/detail?id=148 and http://stackoverflow.com/questions/7895271/getting-the-id-of-an-error-in-elmah-after-calling-raise/7902138 with nearly identical results: “You can’t get there from here, don’t bother.”

The closest work-around is to listen to the ErrorLogModule’s Logged event, and shim it to where you need it:

void ErrorLog_Logged(object sender, ErrorLoggedEventArgs args) {

     string key = args.Entry.Id;

     // stash this somewhere

}

Then after the .Raise() call, you pull it from the stash location.  HttpRequest.Items is request-specific cache, and provides a descent stash location.  But this is at best a fragile hack.

As any good programmer would say, this is a problem that can easily be solved with code.  I downloaded the Elmah source, made some quick adjustments, and posted the solution to http://code.google.com/p/elmah/issues/detail?id=148#c3.  On the off chance that post gets deleted and to add more visibility to the simplicity to the solution, here’s the patch file that shows the (not kidding) 14 lines that made this feature a reality.  If you like this patch too, log into Elmah’s project site and “me too” comment to the ticket.

diff -r 2482ebd0a4ae src/Elmah/ErrorLogModule.cs

— a/src/Elmah/ErrorLogModule.cs    Tue May 01 19:31:20 2012 +0200

+++ b/src/Elmah/ErrorLogModule.cs    Wed May 02 16:13:53 2012 -0700

@@ -84,15 +84,21 @@

 

         protected virtual void OnErrorSignaled(object sender, ErrorSignalEventArgs args)

         {

–            LogException(args.Exception, args.Context);

+            string id = LogException(args.Exception, args.Context);

+            if (!string.IsNullOrEmpty(id)) {

+                args.ErrorId = id;

+            }

         }

 

         /// <summary>

         /// Logs an exception and its context to the error log.

         /// </summary>

+        /// <returns>ErrorId, may be null since not all errors are logged</returns>

 

–        protected virtual void LogException(Exception e, HttpContextBase context)

+        protected virtual string LogException(Exception e, HttpContextBase context)

         {

+            string id = null;

+

             if (e == null)

                 throw new ArgumentNullException(“e”);

 

@@ -105,7 +111,7 @@

             OnFiltering(args);

            

             if (args.Dismissed)

–                return;

+                return id;

            

             //

             // Log away…

@@ -118,7 +124,7 @@

                 Error error = new Error(e, context);

                 ErrorLog log = GetErrorLog(context);

                 error.ApplicationName = log.ApplicationName;

–                string id = log.Log(error);

+                id = log.Log(error);

                 entry = new ErrorLogEntry(log, id, error);

             }

             catch (Exception localException)

@@ -137,6 +143,8 @@

 

             if (entry != null)

                 OnLogged(new ErrorLoggedEventArgs(entry));

+

+            return id;

         }

 

         /// <summary>

diff -r 2482ebd0a4ae src/Elmah/ErrorSignal.cs

— a/src/Elmah/ErrorSignal.cs    Tue May 01 19:31:20 2012 +0200

+++ b/src/Elmah/ErrorSignal.cs    Wed May 02 16:13:53 2012 -0700

@@ -46,15 +46,19 @@

             Raise(e, null);

         }

 

–        public void Raise(Exception e, HttpContextBase context)

+        public string Raise(Exception e, HttpContextBase context)

         {

             if (context == null)

                 context = new HttpContextWrapper(HttpContext.Current);

 

             ErrorSignalEventHandler handler = Raised;

 

+            ErrorSignalEventArgs args = new ErrorSignalEventArgs(e, context);

+

             if (handler != null)

–                handler(this, new ErrorSignalEventArgs(e, context));

+                handler(this, args);

+

+            return args.ErrorId;

         }

 

         public static ErrorSignal FromCurrentContext()

@@ -154,6 +158,11 @@

             get { return _context; }

         }

 

+        /// <summary>

+        /// When you handle the signal, update this with the resulting ErrorId (if any)

+        /// </summary>

+        public string ErrorId { get; set; }

+

         public override string ToString()

         {

             return Mask.EmptyString(Exception.Message, Exception.GetType().FullName);

diff -r 2482ebd0a4ae src/Elmah/ErrorTweetModule.cs

— a/src/Elmah/ErrorTweetModule.cs    Tue May 01 19:31:20 2012 +0200

+++ b/src/Elmah/ErrorTweetModule.cs    Wed May 02 16:13:53 2012 -0700

@@ -129,15 +129,20 @@

 

         protected virtual void OnErrorSignaled(object sender, ErrorSignalEventArgs args)

         {

–            LogException(args.Exception, args.Context);

+            string id = LogException(args.Exception, args.Context);

+            if (!string.IsNullOrEmpty(id)) {

+                args.ErrorId = id;

+            }       

         }

 

         /// <summary>

         /// Logs an exception and its context to the error log.

         /// </summary>

 

–        protected virtual void LogException(Exception e, HttpContextBase context)

+        protected virtual string LogException(Exception e, HttpContextBase context)

         {

+            string id = null;

+

             if (e == null)

                 throw new ArgumentNullException(“e”);

 

@@ -150,7 +155,7 @@

             OnFiltering(args);

            

             if (args.Dismissed)

–                return;

+                return id;

 

             //

             // Tweet away…

@@ -240,6 +245,8 @@

 

                 OnWebPostError(request, localException);

             }

+

+            return id;

         }

 

         private void OnWebPostError(WebRequest request, Exception e)