OpenId Part One: A Friend of A Friend
Posted by: Clarity Blogs: ASP.NET,
on 22 Aug 2009 |
View original | Bookmarked: 0 time(s)
Authenticating someones identity has been a persistent challenge throughout history. How can you trust that someone is who they say they are? Hence the FBI agent flashing her badge, the ancient king brandishing his magical sword, and the long lost relative regaling you with stories of the old times.
Authentication is a two way street. It is in your interest to know who youre dealing with to not get taken advantage of. It is in the other partys interest to make sure no one else is using their identity, impersonating them for their own gain.
When we move into the digital world, authentication is just as important. And the direction has been towards fragmentation and isolation. Your company network knows who you are, your email provider knows who you are, World of Warcraft knows who your are, but Amazon.com could care less. Maintaining all these identities is as much fun as discovering your wallet now doesnt fit in your pocket due to all the preferred customer cards you have to carry around with you.
One solution to this problem is OpenId. With OpenId, you register your identity with an OpenId provider and then can use that identity to authenticate with any other site that trusts (relies on) that OpenId provider. Its much like if youre throwing a party and see someone you dont know. You ask some friends and they say that this person is a friend of theirs, so theyre alright. You still dont really know the guy, but you rely on your friends to vouch for them.
Using OpenId on your site can free you up of the hassle of managing passwords and provide visitors to your site a friendly experience that does not involve creating a remembering yet another password. Within .NET, implementing OpenId is pretty straightforward.
I recently implemented OpenId authentication for an ASP.NET MVC site I was working on. I used the classes and samples provided by the excellent open source library DotNetOpenAuth. I made some tweaks along the way, but before I get to that, lets walk through the sample code that is in the DotNetOpenAuth download.
Although it is in some ways a competitor to ASP.NET WebForms, ASP.NET MVC (MVC) is not named by accident. It is derived from the ASP.NET architecture that WebForms runs on, including the security infrastructure. So for security, you still have FormsAuthentication to work with. For more details on FormsAuthentication, there is a well written synopsis on OReillys site.
Not surprisingly then, we can start in the web.config to see how were going to start hooking up OpenId for authentication:
<
authenticationmode="Forms">
<formsdefaultUrl="/Home" loginUrl="/User/Login" name="OpenIdRelyingPartyMvcSession"/>
<!--named cookie prevents conflicts with other samples-->
</authentication>
This is standard FormsAuthentication, letting the ASP.NET framework know that when authentication is needed, it can look for a cookie called OpenIdRelyingPartyMvcSession and if thats not found, redirect to the /User/Login page. How do we tell if authentication is needed? In MVC, any controller action can be decorated with the [Authorize] attribute.
Getting back to the aforementioned Login page (notice the clean url). It is not much to look at. It has a form with a textbox called openid_identifier and a form action of Authenticate. Ill spare you the rest of the markup, which is readily available from the sample download.
So, we now can look at Authorize action that this form calls:
[ValidateInput(false)]
publicActionResult Authenticate(stringreturnUrl) {
var openid = newOpenIdRelyingParty();
var response = openid.GetResponse();
if(response == null) {
// Stage 2: user submitting Identifier
Identifierid;
if(Identifier.TryParse(Request.Form["openid_identifier"], outid)) {
try{
returnopenid.CreateRequest(Request.Form["openid_identifier"]).RedirectingResponse.AsActionResult();
} catch(ProtocolException ex) {
ViewData["Message"] = ex.Message;
returnView("Login");
}
} else{
ViewData["Message"] = "Invalid identifier";
returnView("Login");
}
} else{
// Stage 3: OpenID Provider sending assertion response
switch(response.Status) {
caseAuthenticationStatus.Authenticated:
Session["FriendlyIdentifier"] = response.FriendlyIdentifierForDisplay;
FormsAuthentication.SetAuthCookie(response.ClaimedIdentifier, false);
if(!string.IsNullOrEmpty(returnUrl)) {
returnRedirect(returnUrl);
} else{
returnRedirectToAction("Index", "Home");
}
caseAuthenticationStatus.Canceled:
ViewData["Message"] = "Canceled at provider";
returnView("Login");
caseAuthenticationStatus.Failed:
ViewData["Message"] = response.Exception.Message;
returnView("Login");
}
}
return new EmptyResult();
}
While there is a distinctly pasta like consistency to this code, it is demo code and gets the job done. The first thing to understand about this method is that it is meant to be called twice. OpenId authentication involves hopping outside of your sites domain, letting the user log in to the provider site, and then reading back the response from that provider.
Hence the strange looking conditional around the response variable. When a user types in an OpenId identifier, such as http://myname.myopenid.com and clicks log in, that request goes to our site first. At this point, there is no OpenId response, since we havent asked for anything yet. So, with a null response, we send out a request to the OpenId provider, wholl get back to us when the user logs in.
The second time this method is hit, it is being hit by the OpenId provider, who passes along a bunch of data that the DotNetOpenAuth library kindly parses for us into a response object. If the request was authenticated, we can complete the final step of FormsAuthentication, which is to create a cookie with authenticated users info so they dont have to log in for every request. With the users OpenId identifier verified, we send them on their way with a redirect back to the page they first requested.
I hope you enjoyed this brief look at OpenId and the DotNetOpenAuth library. Up next, linking an OpenId identifier back to objects in our domain for the purpose of access control.