Cache and Query The life of a data-head: Obtain, store, invalidate, repeat.

7Feb/120

The right way (for now) to use HTML5 History API (?)

First off, the astute reader will note the question mark in the title above.  The disclaimer on this article is that this method is right *for me* and in my opinion seems to be more complete than some of the other workarounds I've seen on the internet.  Your mileage may vary, and in fact, you may just want to go download history.js right now and not mess with any of this.  I wanted to try to do it completely within the bounds of the native HTML5 APIs and the is the best solution I was able to come up with.

In my tests, this works with Chrome 16, FF 10, Safari 5 (all on the PC) and in fact, this was the ONLY way I was able to get consistent results across all those browsers.  IE10 should support history, but I only had IE9 on this machine, so I don't know. The reason that this was the only way I could get it working is because of an inconsistency between Webkit and Firefox.  In Webkit browsers, the onpopstate event fires on the first page load. I believe this is the wrong behavior (and this issue seems to support my belief).  In Firefox, the onpopstate event only fires (correctly) on subsequent page loads. The problem comes in when trying to do something consistent across both browsers on initial AND subsequent page loads without having the events step on top of each other. Ok, so with all that behind us, here's the code (heavily commented with explanations):

(Oh yeah, I'm also using Modernizr to detect if the browser supports the history API -- if you're not using Modernizr in your project, you're seriously missing out.)

	(function ()
	{
		var _firstLoad, _id = null; // the value of _id would be set by your server side code on page load if it had something to pre-load from the URL

		// in this function, i'm assuming you'd use the id passed in to do some ajax call and update the page
		var loadContent = function (id, push)
		{
			if (id != null)
			{
				// ajax call to your server to pull data
				//
				// ... [ your code here] ...
				//
				// update page with pulled data

				// if browser supports history (check using Modernizr) *AND* we've passed true to push, then we pushState
				if (Modernizr.history && push)
					history.pushState({ id: id }, "", "/" + id); // update the url to something like /5 or /6 (depending upon id)
			}
			else
			{
				// load whatever the "initial" or empty page would be in your application
			}
		};

		// in case you want to see the order in which these fire
		//alert('code');

		window.onload = function ()
		{
			// in case you want to see the order in which these fire
			//alert('load');

			// every browser properly fires the onload event, so in this, we set a boolean value when it is the first page load
			// we also use replaceState to replace the current state with whatever property we currently need to display.
			// Since this only fires on the FIRST load, we don't have to worry about messing any pushStates up, plus we ensure there's something in the
			// history if/when we return to this page later

			_firstLoad = true;

			// if we have a value for _id on first load, we use REPLACEstate to make sure this is on the history stack
			if (_id != null && Modernizr.history) history.replaceState({ id: _id }, "", "/" + _id); // update the url to something like /5 or /6 (depending upon _id)

			// since we used replaceState, we don't want to push something on the history stack on top of it, so we load the content,
			// but pass false to avoid it getting pushed on the history stack
			loadContent(_id, false);

			// this is the only "hacky" part of what we have to do, but this makes the _firstload = false not fire until AFTER the onpopstate fires
			setTimeout(function () { _firstLoad = false; }, 0);
		};

		// check to see if their browser even supports history API using Modernizr
		if (Modernizr.history)
		{
			window.onpopstate = function (event)
			{
				// in case you want to see when this event fires
				//alert('pop');

				// this is the key to everything right here.
				// this event fires in webkit on page load AND history pops, but in firefox, it only loads on history pops
				// so, to standardize, we use the _firstLoad value from earlier to see if this is a first page load
				// if it is a first page load (that means we're in Webkit) and we just exit out but we set _firstLoad = false
				// so that we can properly handle future pops, but this way, we don't get any pops on the initial page load,
				// regardless of what any goofy browser wants to do on the inital page load.

				if (_firstLoad)
					_firstLoad = false;
				else
				{
					if (event.state != null)
						// there is something in the history state for this entry, so we go ahead and load it
						// but we pass in false so that it doesn't write another entry over it...
						loadContent(event.state.id, false);
					else
						// if there is nothing in the state (either first load or returning to a page that was a first load)
						// then we tell it not to load the ajax, but instead just load the default content
						loadContent(null, false);
				}
			};
		}

	}).call(this);

So, there you have it.  If you've never done anything with HTML5 History, that probably looks like a hot mess.  (If you want a good intro to HTML5 history, start here.)  But read the comments and plug in your own data/scenario and I think you'll see its actually fairly straight forward and versatile for any scenario.  I've used this in two separate applications now with great success regardless of page load time or the complexity of the scripts involved.

If you're looking to do history updates, hopefully this helps you avoid banging your head on the desk for hours like I did.

- Jorin

Filed under: Technology No Comments
6Feb/122

Final Saddle Stool Sitting/Standing Desk Setup

Well, it's been more than two weeks since my last post, but that's actually because I've been pretty busy -- and that is a good thing!  Around the time I wrote the other two posts, I was in so much pain, I really wasn't as productive as I'd like to have been, and believe me, this is much nicer.  I've actually just wrapped up one side project and am getting very close to a stable release on my primary work project, but all that is for another post.

And all in all, I believe I have my new work setup to thank for my increased productivity. My back is not magically healed, but it is definitely better. I'm still at my computer for 10 or more hours each day, but now when I get up, I don't have to spend a few minutes letting my spine re-align itself.  I did also visit a chiropractor friend of mine, but he said no prolonged treatment was necessary in light of the other changes I'm making.  He recommended I come into get adjusted whenever something is seriously out of whack and he also suggested some stretches and exercises I can do on my own.  I'm still confident I have herniation from my now-18-year-old injury, but didn't want to fork over the cash to get an MRI to confirm what I already know.

Anyway, after my last post I still wasn't perfectly pleased with the setup I had created for myself.  I lowered my desk to accommodate the stools I had, but it was too low to keep good posture.  And then, when I grew tired of sitting in the stool and wanted to stand, the desk was WAY too low.  Also, from what I read online, neither of the two stools I had were a good height for me.  So, fortunately, I tested out a saddle stool Mark Rippetoe used previously at my gym.  This one (SAV-015-B) extended up to 30" (in my experiences, it's actually taller -- see measurements below) and had a much better designed seat.  So, I borrowed his for a few days to test it out and take some measurements.  I calculated the optimal height for my desk and monitors such that nothing really needed to change when I would switch from sitting to standing.  Once I had some measurements, I ordered my own stool and was all set to custom build some blocks for my desk and a new "hutch" for my monitors.  I got in touch with my favorite craftsman and overall handyman Randy Blair to get his opinion on what kind of wood to use so it wouldn't look like I had lumber sitting on top of my desk, and he actually ended up just going home and building me the pieces I needed (the nerve of some people :-) ).  I sanded down everything and had Cassie help me pick out some paint colors and voila!

Final Desk Setup

I'm very pleased with how it looks, my back is pleased with how it feels, and my productivity is pleased with the way it works!

In the end, here are some current measurements in case it helps anyone else out:

  • I'm about 5'10" (5'11" in shoes)
  • I have about a 31" inseam
  • I have the saddle stool set slightly below its max height and it measures 32" to the top of the back of the seat (under no load; it compresses when I sit in it)
  • The blocks under the desk are 12-1/4" tall
  • My desk is 29-7/16" high
  • That makes the total height of my desk off the ground right at about 42"
  • The monitor stand is 6-1/4" tall
  • Right now, the tops of my adjustable monitors are 20-1/2" off the desk (62-1/2" off the ground)

Now, the monitors are actually slightly below eye level at the moment.  I settled on this height because its perfect for when I sit (btw, when I sit, I'm about 2" shorter than when I stand).  And then, when I stand, it's still comfortable while allowing me to look out the window behind my monitors. :cool:

So, hopefully this all helps someone out there and also hopefully this is my last post or dealings with any of these issues related to my back pain.  I'm looking forward to cutting back on the work hours in the coming months, and hopefully also sticking with these stretches/exercises to keep my back healthy and functional for years to come, but I feel a lot better knowing that even when I do have to work long hours, I can do so in a position that keeps my back from getting worse and worse.

- Jorin

Tagged as: , , 2 Comments
9Jan/120

Regulators… Mount Up

Well, it's been longer than two weeks but this is the much anticipated :-) follow-up to my last post. The good news is my back does feel better but unfortunately no miracles. And, just in case you didn't know, standing for 8+ hours isn't very awesome at all. I'm glad these two weeks coincided with the holidays because it meant I didn't work quite as much as I would have otherwise. My knees, ankles, and calves were crazy sore. Granted, standing is better for you than sitting, but I don't recommend anyone enter into something like this lightly. It is hard and made me fairly cranky by the end of each day. Another interesting side effect of standing is I felt rushed and like I was always about to go somewhere for the first few days, but that may have just been me.

Well, after 2 weeks I decided standing wasn't a great long term solution. So, I ordered a saddle chair!

20120109-100556.jpg

Giddy-up

Actually, I ordered two (you can kinda see the other one in the background) to test my options (thank you Amazon Prime with free returns). Anyway, my knees, calves, and ankles thank me for switching to a chair, but I am most definitely saddle sore. The logic behind a saddle chair is that when you sit with your legs in front of you, you automatically put your spine into flexion rather than extension which only exacerbates the pain from external herniations and bulges such as the one I have. With a saddle chair, your legs hang to each side, more vertically, which allows you to keep your spine in extension when sitting. It also promotes blood flow to the legs as opposed to cutting off that circulation when you sit in a traditional chair. You are also supposed to keep your feet behind the center piston of the chair to further aid in keeping everything in line. It came with a back rest but that would totally defeat the purpose so it's still in the box.

Anyway, for the nest two weeks I have another experiment to endure! Cowboys aren't saddle sore forever so I'm sure I'll get used to it eventually and at least my chair doesn't gallop and bounce around violently. And in all honesty the saddle pain isn't anything compared to the back pain. In fact as I write this I'm in a waiting room for my wife's doctor appointment and it hurts to stand up from these chairs. In the saddle, when I stand up at least I feel good and can walk around without waiting for my spine to re-align.

The only other issue of note is that I lowered my desk (from 16" supports to 8" supports) to accommodate the chair because it won't extend high enough to match my standing desk height and that kinda sucks because now its too low when I stand, but if this works out long term, I'll find a decent intermediate height for the desk.

We'll see you again in two weeks!

Tagged as: , , No Comments
19Dec/110

Pain in my …. back

When I was 12 years old, I herniated a disc in my lower back and have had chronic back pain ever since.

Because I've been dealing with it for so long, I've become quite adept at identifying the severity of the pain as well as dealing with it. I can usually identify ahead of time if my back is going to "go out" and try to do things to prevent it.  When it does flare up, I know exactly the positions to get in to pop or stretch it depending upon what the situation dictates.  I know what exercises to do to strengthen my back, and I know the exact exercises that will cause my back to be "tired" and therefore weaken it.  Overall, I work very hard to do several things to strengthen my back ... and in doing so, I've had great personal success.

But lately, I've been having problems again and upon closer inspection, I believe its because of all the time I don't spend focusing on my back.  Said plainly, I think its because of all the time I spend sitting on my ass.  A quick count reveals that there are no less than 35 "seats" in my house!  In a given day, I spend about 14 of my 16 waking hours in a seated position (at work, eating, on the toilet, in a car, at bible class, on the couch)! The amount of time I spend actively trying to keep my back in good health pales in comparison to the amount of time I spend passively destroying it. And that's on a night where I sleep 8 hours.  If I'm up late programming on a side project, I'm only doing further damage.

And yes, I know I should sit up straight the whole time, but I also know no one on this planet does that.  Its practically impossible.  The amount of effort required to keep your back in isometric extension while performing a dead lift is phenomenal -- and that's when the dead lift is the ONLY thing on your mind.  Keeping your back in that same extension while also focusing fully on a project or program is arguably much more difficult. When I'm coding, I'll get focused on something and next time I look up, its 3 or even 4 hours later.  And unknowingly for that whole time, I've been slouched back and to my right (mouse-hand) side.  So then I try to get up and I can't even stand straight for a few minutes.  The end result is probably counter-intuitive to most, but my back feels better after squatting and dead lifting 400 pounds than it does after sitting at a computer for a day.

Anyway, today I hit a breaking point.  I was at the gym, and my back was so weak that every single lift was either painful or impossible. It has been bad for the last 6 weeks or so (since I've been so busy at work), but today was a whole new level of bad.  I left the gym and went straight to Home Depot.

On Blocks

The end result is my very own "Standing Desk." It's a little more .... well, "urban" than I'd probably prefer, but this is just a test and quality standing desks are expensive. This raised my desk 16" and cost less than $20.00.  It's a little higher than I thought I needed (I was shooting for 14") but after getting it set up it actually feels really good.

So, I'm going to try this for at least two weeks.  I'm sure my feet will be tired at first, but I think its worth it.  And if I really like it, maybe I'll look into a nice standing desk... or maybe I'll just paint/cover the cinder blocks. :-)

Tomorrow I'll actually be out of the office, so the first full day's experience will have to wait till Wednesday, but we'll see how it goes, and I'll post back here in two weeks.

EDIT:

My lovely wife reminded me of this today.  No post about a standing desk would truly be complete without it: http://www.nbc.com/the-office/video/this-will-not-stand/1371438

Tagged as: , , No Comments
25Apr/110

Downgrading iOS Apps

Last week I upgraded several apps on my iPhone and usually that process is smooth and results in awesome new features/bug fixes for all the updated apps. This time, unfortunately, the opposite was the case. Zynga recently bought the "with Friends" franchise of apps from Newtoy, Inc, and their update of Chess With Friends made the app practically unusable. The new graphics were pretty ugly, in my opinion, and the game play was painfully slow. So, it wasn't hard to ultimately decide to roll back the install until Zynga gets all this worked out. However, I'd never actually had to roll back an app before. Once I got it figured out, I thought I'd post here to help anyone else out.  These instructions are written for a PC, but the process on a Mac would be pretty similar.

The easy way first. If you updated from your device and haven't updated on your PC yet, just delete the app and sync with your PC to get the version from the last device sync. However, if it were always that easy, then there wouldn't be a need for this post. So, now, the hard(er) way...

This is assuming you've updated the app both on your PC and on your device and therefore cannot just simply delete and re-sync. If that is the case, first start by deleting the app on your device (don't worry, if this doesn't work, you can always re-download the latest version from the iTunes Store and you'll be no worse for wear... even if its a purchased app).

Deleting an app

Then go into your Mobile Applications folder on your PC (usually C:\Users\[your user name]\My Music\iTunes\iTunes Media\Mobile Applications but your folder may be different depending upon your versions of Windows and iTunes). If you can't find it, you can just search for *.ipa files and go from there. In the screenshot below, you can see that we've found the old version of the app we want (the new version is 4.01 and the old version is 3.07). Copy that file to your desktop or somewhere to make sure you have a good copy. If you can't find the file in that folder, do a search in your Recycle Bin for *.ipa files. iTunes is nice enough to put old files there as it updates, so usually you can find it there if you can't find it in your Mobile Applications folder.

Finding the .ipa file

Once you have it on your desktop, drag it to your iTunes Library. It will ask if you want to overwrite the current version. Of course you do! From there, now the right version is in your library, so you can just sync with your device and you should be good to go! (Don't forget to re-select the app in iTunes to sync to your device).

Drag to iTunes

Silly Prompt

Make sure and sync the app

Now, the only thing to be aware of is that when you download future updates on your device or computer, you'll have to avoid downloading the update for the app you downgraded by not choosing "Update All" and instead updating all the other apps one-at-a-time. It becomes kind of a pain, and you'll always have that annoying red "1" on your device's App Store icon letting you know there's an update, but hopefully the company in charge of the bummed app (I'm looking at you, Zynga) will get it straight soon and you won't have to put up with that for long.

Have fun!

P.S. If, by chance, you are reading this and you need the old version of Chess With Friends because you can't find it on your computer anywhere, here it is in a zip file.

Tagged as: , , No Comments
1Mar/1123

Customizing ASP.NET MVC Basic Authentication

Introduction

About a week ago, I set out to add authentication to an API for a small project I was working on. I didn't need the complexity of something like OAuth and for an API, Forms Authentication doesn't make much sense. I figured the easiest would be to just enable Basic Authentication in IIS and I'd be on my way. Well, the first problem with using Basic Authentication as it comes in IIS is that it only connects to Windows accounts, which in my case wouldn't work; I needed to authenticate against a database. So, I started Googling... without much immediate success. My two most successful results were here and here. The first one wasn't bad, but it also wasn't complete enough for me to wrap my head around it and make it work. The second, by Alex James, is awesome, but it's written more in the context of WCF and therefore uses HTTP Modules and I also wasn't too fond of the way he suggested handling allowing unauthenticated access to some endpoints. I really liked the simple beauty of the [Authorize] attribute in MVC and wanted to go more in that direction. So, I combined the two and with a little bit of my own special sauce, came up with what you'll see here. It allows for flexibility in use (selectively tagging actions and/or controllers) and also in structure (dependency injection using Ninject). An added bonus I discovered is that it doesn't even depend on IIS since all we are really doing is returning a standard 401 HTTP error code and adding another header to the response stream.

Getting Started

First thing we need to do is make sure all authentication (besides anonymous) is turned off in whatever server you are using, and that our app is configured to do no authentication of its own (in web.config).

<authentication mode="None" />

IIS 7 Authentication

Anonymous authentication enabled in IIS 7

Now that all other authentication is turned off, we'll create a custom attribute to do the our custom authentication against our custom data store.

Making It Work

This is the full source for the class that defines the CustomBasicAuthorize attribute that we'll use to decorate our controllers and actions that we want to secure.

	public class CustomBasicAuthorizeAttribute: AuthorizeAttribute
	{
		bool _RequireSsl = true;
		public bool RequireSsl
		{
			get { return _RequireSsl; }
			set { _RequireSsl = value; }
		}

		// These lines of code are only required when using Ninject for
		// constructor dependency injection with a parameterless constructor
		// of this attribute.
		[Inject]
		public IUserRepository Repository { get; set; }
		public CustomBasicAuthorizeAttribute()
		{
			// NinjectHelper is a static helper class that just exposes the current kernel object
			// check the code in NinjectHttpApplicationModule.cs to see where the kernel is initialized
			NinjectHelper.Kernel.Inject(this);
		}
		// end DI code

		private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
		{
			validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
		}

		public override void OnAuthorization(AuthorizationContext filterContext)
		{
			if (filterContext == null) throw new ArgumentNullException("filterContext");

			if (!Authenticate(filterContext.HttpContext))
			{
				// HttpCustomBasicUnauthorizedResult inherits from HttpUnauthorizedResult and does the
				// work of displaying the basic authentication prompt to the client
				filterContext.Result = new HttpCustomBasicUnauthorizedResult();
			}
			else
			{
				// AuthorizeCore is in the base class and does the work of checking if we have
				// specified users or roles when we use our attribute
				if (AuthorizeCore(filterContext.HttpContext))
				{
					HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
					cachePolicy.SetProxyMaxAge(new TimeSpan(0));
					cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
				}
				else
				{
					// auth failed, display login

					// HttpCustomBasicUnauthorizedResult inherits from HttpUnauthorizedResult and does the
					// work of displaying the basic authentication prompt to the client
					filterContext.Result = new HttpCustomBasicUnauthorizedResult();
				}
			}
		}

		// from here on are private methods to do the grunt work of parsing/verifying the credentials

		private bool Authenticate(HttpContextBase context)
		{
			if (_RequireSsl && !context.Request.IsSecureConnection && !context.Request.IsLocal) return false;

			if (!context.Request.Headers.AllKeys.Contains("Authorization")) return false;

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

			IPrincipal principal;
			if (TryGetPrincipal(authHeader, out principal))
			{
				HttpContext.Current.User = principal;
				return true;
			}
			return false;
		}

		private bool TryGetPrincipal(string authHeader, out IPrincipal principal)
		{
			var creds = ParseAuthHeader(authHeader);
			if (creds != null)
			{
				if (TryGetPrincipal(creds[0], creds[1], out principal)) return true;
			}

			principal = null;
			return false;
		}

		private string[] ParseAuthHeader(string authHeader)
		{
			// Check this is a Basic Auth header
			if (authHeader == null || authHeader.Length == 0 || !authHeader.StartsWith("Basic")) return null;

			// Pull out the Credentials with are seperated by ':' and Base64 encoded
			string base64Credentials = authHeader.Substring(6);
			string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(base64Credentials)).Split(new char[] { ':' });

			if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[0])) return null;

			// Okay this is the credentials
			return credentials;
		}

		private bool TryGetPrincipal(string userName, string password, out IPrincipal principal)
		{
			// this is the method that authenticates against my repository (in this case, hard coded)
			// you can replace this with whatever logic you'd use, but proper separation would put the
			// data access in a repository or separate layer/library.
			UserBase user = Repository.Authenticate(userName, password);

			if (user != null)
			{
				// once the user is verified, assign it to an IPrincipal with the identity name and applicable roles
				principal = new GenericPrincipal(new GenericIdentity(user.UserName), user.Roles);
				return true;
			}
			else
			{
				principal = null;
				return false;
			}
		}
	}

I also added a new subclass called HttpCustomBasicUnauthorizedResult. This just kept the code cleaner, IMO, but the real important part is making sure that the "WWW-Authenticate" header gets passed back with the response. This, in combination with the 401 HTTP Error that is assigned by the base class, is what makes the client know that basic authentication is being required.


	public class HttpCustomBasicUnauthorizedResult: HttpUnauthorizedResult
	{
		// the base class already assigns the 401.
		// we bring these constructors with us to allow setting status text
		public HttpCustomBasicUnauthorizedResult() : base() { }
		public HttpCustomBasicUnauthorizedResult(string statusDescription) : base(statusDescription) { }

		public override void ExecuteResult(ControllerContext context)
		{
			if (context == null) throw new ArgumentNullException("context");

			// this is really the key to bringing up the basic authentication login prompt.
			// this header is what tells the client we need basic authentication
			context.HttpContext.Response.AddHeader("WWW-Authenticate", "Basic");
			base.ExecuteResult(context);
		}
	}

Putting It To Use

For this sample app, we'll create a simple AccountController with two methods. One, called Authenticate, will not be secured (so we can get authenticated) and the other, called Me, will require authentication and display information about the logged in user.


	public class AccountController : Controller
	{

		// this is only necessary if you are doing dependency injection
		IUserRepository _UserRepo;
		public AccountController(IUserRepository userRepo)
		{
			_UserRepo = userRepo;
		}
		// end DI code

		public ActionResult Authenticate()
		{
			return Json("This is open for unauthenticated access.", JsonRequestBehavior.AllowGet);
		}

		[CustomBasicAuthorize]
		public ActionResult Me()
		{
			return Json(_UserRepo.GetDetails(User.Identity.Name), JsonRequestBehavior.AllowGet);
		}

	}

And in this case, the repository (as seen below) is just validating against or supplying some hard coded values. In this case, the username/password combination of "user" and "pass" will get you logged in and then once you are logged in, it will display those same hard coded values as JSON.


	public class HardCodedUserRepository: IUserRepository
	{
		public UserBase Authenticate(string userName, string password)
		{
			// you would likely query the database here instead...
			if (userName.ToLowerInvariant() == "user" && password == "pass")
				return new UserBase { Id = "1", UserName = "user", FirstName = "Some", LastName = "User", Email = "user@user.com", Roles = new string[] { "Admin", "Manager" } };
			else
				return null;
		}

		public UserBase GetDetails(string userName)
		{
			// you would likely query the database here instead...
			if (userName.ToLowerInvariant() == "user")
				return new UserBase { Id = "1", UserName = "user", FirstName = "Some", LastName = "User", Email = "user@user.com", Roles = new string[] { "Admin", "Manager" } };
			else
				return null;
		}
	}

Authenticated JSON Output

The nice thing about this model is that since it uses an IPrincipal, all the parts of authentication you are probably already used to still work. You can use User.IsInRole() for manual testing of privileges or you can add properties to your attribute to only allow certain users or roles to specific actions or controllers. For example, an attribute like:

[CustomBasicAuthorize(Users="user, otheruser", Roles="admin, manager")]

will allow only the specified users and roles to access whatever action/controller it is decorating.

Wrapping Up

So, with only two classes (not counting any of the repository or dependency injection code), we've now created a very extensible way to easily secure any/all of our API methods so that any client can now pass credentials using a standardized protocol!

One thing that I hope goes without saying is that if you are using Basic Authentication for anything, it should always be done over SSL. Even though the credentials are Base64 encoded, they are still just plain text and can be easily decoded and viewed by anyone inspecting the transmission (it only took us one line of code to decode it above).

The full source code for this project is available here: Download the code (CustomizedBasic.zip) on GitHub.

Happy Coding!