So, I’ve been a really happy CSLA.NET user for a while, but I’ve been finding that
generating the code has becoming tedious and my development velocity has been slowing
down on my CSLA libraries. And so, rather than getting back involved with blood sacrifice,
I figured I’d experiment with another layer in there. I’m currently working up some
code with LLBLGen Pro (because
llblgen lite is for suckers!), and it’s working great….but Rocky made it so easy
to do forms authentication with CSLA. It was just a couple’a pages in the book and
it worked just fine and ….well…. I don’t understand how it works. Now that I’m
not including his framework, I need to figure it out.
So, follow along with me as we figure it out. Here’s how it’s done.
Step 1: Some configuration
First thing I tried was putting a Label on a form and assigning HttpContext.Current.User.ToString()
to it. The result? “System.Security.Principal.WindowsPrincipal”
Right. My application is still out-of-the-box set to Windows authentication (Kerberos.
ActiveDirectory. NSA Backdoor.) Let’s change that.
<?xml
version="1.0"?> <configuration> <system.web> <authentication mode="Forms" /> </system.web> </configuration>
>
Good. What’s in that label now? “System.Security.Principal.GenericPrincipal” Much
better.
Step 2: The Password is ‘Joshua’!
This step is a big one. There are 27 members defined in the MembershipProvider contract,
so go get some coffee, lithium, and a rotisserie chicken, and we’ll get started.
Make a new class, call it MyMembershipProvider.cs. Make it extend MembershipProvider:
using System; using System.Web.Security; using System.Web; namespace Website1
{ public class MyMembershipProvider
: MembershipProvider
>
Go ahead and let Visual Studio define all of the inherited members. We’re only going
to change one:
public override bool ValidateUser(string username, string password)
{ return true;
}
>
Now we’ve got another little configuration change to make.
<configuration> <system.web> <authentication mode="Forms" /> <membership defaultProvider="HscMembershipProvider"> <providers> <add name="MyMembershipProvider" type="Website1.MyMembershipProvider" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="false" applicationName="/" requiresUniqueEmail="false" passwordFormat="Clear" /> </providers> </membership> </system.web> </configuration>
>
Go head and drop a Login control and a LoginStatus on the form and try it out. It
logs you in! No matter what you enter! Sure, you could put in some code that checks
against your database to see if people are allowed in or not, but that’s just elitist,
isn’t it? Creating an ‘In-group’ and an ‘Out-group’ and segregating them? Next thing,
you’ll be wanting to beat up blue eyed people because they prefer Lord of the Rings
to Star Wars. Well, if you’re going to put in some authentication code, go for it.
I won’t stop you.
Step 3: The Principal’s Orifice
Ok. So there’s a little gotcha at this point. If all you’re looking for is a login
and password check…then it’s time to feed your brain to the zombies, ’cause you
won’t need it anymore. But if you want more than just a username logged in, we’ve
go some stuff to do. Forms auth never gives you anything but the GenericPrincipal.
It will be marked ‘IsAuthenticated’ if it is…and you can use the Roles provider
to fill in the roles if you want. It also looks like it’s possible to fill in the
roles manually using an event handler in Global.asax.
But here’s the thing. While I really dig on the login functionality of ASP.NET Membership,
I’m not so convinced that Roles or Profiles provide a meaningful, robust implementation
that’s useful for more than a toy web forum. Can you give the branch manager for the
Salem office full privileges to the customers there without letting him check on President
Obama’s secret pornography transactions at the Hillsboro branch? With just IsInRole(string)?
It sure doesn’t seem like it. The major advantage of Profiles seems to be that you
can use web.config to define the fields….which is great until the point where you
want to find all users within 50 miles of Denver….and then you’re stuck in an abandoned
warehouse with the vampires. Serialized XML doesn’t query too very well. There’s probably
solutions to these problems, but not out-of-the-box and why work really hard to have
a decoupled design with a provider model that is so customized and complicated to
implement that you won’t ever be swapping it out?
This is a job for a Custom Principal. That’s what it is. Implement just enough to
make the ASP.NET controls happy and then add the real meat to your own object. The
principal also encapsulates an IIdentity object. You could go ahead and implement
both of these, but since I’m lazy and want to go play Rock Band, I’m going to do both
in one
fell swoop. (Note that this is untested, and may put cracks in your Dilithium
Crystals requiring embarrassing compromises with pimps.
using System; using System.Security.Principal; namespace Website1
{ [Serializable()] public class MyPrincipal
: IPrincipal, IIdentity { public static MyPrincipal
Login(string username, string password)
{ MyPrincipal loginUser = new MyPrincipal(); if (!string.IsNullOrEmpty(password))
{ loginUser.Name = username;
loginUser.IsAuthenticated = true; return loginUser;
} return loginUser;
} public bool RussianRoulettePermission()
{ Random random = new Random(); if (random.Next(1, 6) == 3) return false; return true;
} #region IPrincipal
Members public IIdentity
Identity { get { return this;
} } public bool IsInRole(string role)
{ return false;
} #endregion #region IIdentity
Members public string AuthenticationType
{ get { return "Custom";
} } public bool IsAuthenticated
{ get; private set;
} public string Name
{ get; private set;
} #endregion }
}
>
I accept that you and your fascist friends will probably want to do more password
checking than making sure that the user typed a letter. Good
fences make good neighbors, right? The real point is that you can put your own
Crazy User Code in here and have it available when your pages want to use it.
Now we can fix the Membership Provider to use our new shiny principal object!
public override bool ValidateUser(string username, string password)
{ HttpContext.Current.User = MyPrincipal.Login(username,password); return HttpContext.Current.User.Identity.IsAuthenticated;
}
>
Give it a try!
Step 4: Dammit!
I set Current.User to my Principal. I did. I did. I did. Why doesn’t my damn label
change from GenericPrincipal when I log in? It’s cheesin’ me off here!
The problem here is the Forms Authentication uses it’s own principal and sets the
context to it on each page load. This is what it’s supposed to do, but we want better!
We’re going to add a few methods and fix the problem. One caveat here (and why do
people want to eat caves? And which Cave are you At when that happens? Where’s my
lithium?): I’m going to trust the forms authentication cookie. I think that’s enough
of a ticket to bypass password authentication. If you don’t, you might want to jump
through some extra hoops. I’m also trusting that my developers won’t use the bypass
methods to create secret login backdoors and the like. I figure if you can’t trust
the coders then…well, you work at my office. But anyways….
To the principal!
public static MyPrincipal
GetLoggedInUser(string username)
{ MyPrincipal loginUser = new MyPrincipal();
loginUser.Name = username;
loginUser.IsAuthenticated = true; return loginUser;
} public static MyPrincipalGetLoggedOutUser()
{ return new MyPrincipal();
}
>
And in Global.asax!
protected void Application_AcquireRequestState(object sender,
EventArgs e) { if (User.Identity.IsAuthenticated)
{ HttpContext.Current.User = MyPrincipal.GetLoggedInUser(User.Identity.Name);
} else {
HttpContext.Current.User = MyPrincipal.GetLoggedOutUser();
} }
>
Step 5: And now for a magic trick.
Happy Day! Kill Ugly One-Horned Mule!
My cheap and dirty Membership implementation works. Now I can put this in my Form_Load
method:
Label1.Text = "You're
Logged in: " +HttpContext.Current.User.Identity.IsAuthenticated + " And
you're dead!: " + ((MyPrincipal)HttpContext.Current.User).RussianRoulettePermission();
>
Sweet! You can find out if you’re logged in. And if you’re dead. All at the same time.
Next Steps: Drunk on Cookie Magic
Obviously we’re going to need some code to check passwords. Probably fetching against
the database. No problem. I’m planning on encapsulating my DTOs from LLBLGen into
my principal. That way I can expose read access to user objects for the logged-in
users. I can also put in complex permissions right there. I’m going it my way.
Sweet!
It’s worth noting that this implementation goes off to the DB for each page load.
That can sure suck up the performance points under a real load. However, a little
bit of effort can store the principal in the session object and then you get it back,
deserialized, instead of requiring a round-trip. Just remember to code for the case
where the session expires, but the forms cookie does not.
until next time, remember: the skin is the best part.