OpenId Part One: A Friend of A Friend
Authenticating someone’s 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 you’re dealing with to not get taken advantage of. It is in the other party’s 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 doesn’t 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. It’s much like if you’re throwing a party and see someone you don’t know. You ask some friends and they say that this person is a friend of theirs, so they’re alright. You still don’t 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, let’s 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 O’Reilly’s site.
Not surprisingly then, we can start in the web.config to see how we’re 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 that’s 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. I’ll 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 site’s 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 haven’t asked for anything yet. So, with a null response, we send out a request to the OpenId provider, who’ll 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 user’s info so they don’t have to log in for every request. With the user’s 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.