Contact   •   Products   •   Search

Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs

Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth


The #1 request I got on the CodePaste.net site - which provides a quick and easy way to publish and share code snippets publicly - has been to implement OpenId for authentication rather than the custom username/password login that’s been in use up until now. OpenId is a centralized login/authentication mechanism which handles authentication through one or more centralized OpenId providers. These providers live on the Web and are accessed through a forwarding and callback mechanism – you log in at the provider’s site and are then returned to the original starting Url with an authorized user token that uniquely identifies that user to the original site.

What is OpenId?

OpenId is a single sign-on scheme – the idea is that you keep your login and profile information in one place so that you don’t have to login at every Web location and create new user credentials on each site. The  idea of a single sign on isn’t new of course – lots of these things have been around over the years with the most remembered probably being Microsoft’s Passport/Windows Live ID (not that anybody likes Windows Live Id). OpenId uses a similar concept, but the difference is that there are many providers and  sites that are implementing OpenId Providers so that you can use their log on credentials that you are already using to log into other sites. Chances are that you already have a sign on on one of the OpenId providers rather than just one provider that controls access. You can essentially choose where you log in from.Some of sites that are OpenId providers now are Google, Yahoo, Microsoft Windows Live, AOL, Facebook, Flickr and many many more. Chances are you’re signed up on one of these services and you can use their logons to log in to other sites that support OpenId. The rush to OpenId is fairly fresh so some of these work better than others. The ones I’ve been using are MyOpenId, Google and Yahoo of which MyOpenId works best if you want to share profile information.

To be honest I hadn’t warmed up to OpenId until recently. In fact I had to be convinced by user requests on the CodePaste.net site to take a look at integration. Now that it’s there I think it is a pretty sweet way to go although I think the concept is lost on the average user until OpenId becomes more wide spread. It also didn’t help that the original implementations and providers were pretty crappy and the process on most sites to sign up actually was really slow and clunky. That has changed a bit with better interfaces that are faster and many more sites are using OpenId and many more providers exist.

Another pain point is that supporting standards for retrieving profile information like Simple Registration (SREG) is not widely supported. Providers are returning information in a variety of different formats and most don’t return profile information at all whether you approve or not which is really lame. You presumably own the data on any service you’ve given data to so why the fuss of sharing it if you approve of sharing? Anyway, don’t count on getting full contact information from OpenId for a while. Some providers will give some information, others won’t give any – so your application has to be able to handle both scenarios.

OpenId Development

For development with .NET there are a few libraries out there that handle the communications details for you. While communications are handled using simple URLs for routing and callbacks the data encoded into these URLs must be secure so that they can’t be spoofed and this process is not exactly trivial.

For the site I chose DotNetOpenAuth – after doing a little bit of research it seemed to me that this was the most active development project that is also most feature rich. DotNetOpenAuth does much more than OpenId it also supports oAuth (a non-interactive service based counterpart to OpenId) which is another thing on my list to implement for the API portion of the site. DotNetOpenAuth  is fairly comprehensive and includes support specifically for Web applications – both MVC and Web Forms with controls and helpers provided as well as MVC specific action methods to handle redirection and return routing which is a nice touch.

The tool makes the actual routing and handling of the results fairly easy, but the integration process nevertheless is not completely trivial. The reason for this is that most applications will need to handle both logins as well as attaching an OpenId account to an existing user profile of some sort and this can get tricky because of the HTTP GET based callback interface. The actual OpenId calls and callbacks are quite simple – the actual user interface and hookup routines though ended up taking me a lot longer than I had expected.

Walking through OpenId Authentication

Alright lets take a look and see how this works on the CodePaste.net site. As I mentioned there are two parts – the basic login validation which is very straight forward and simple so I’ll start there.

The login form displays both logins for OpenId and the traditional username and password:

OpenIdLogin

Figure 1 – Logging in with OpenId

I needed to leave the old login in form place so existing users can log in and attach their existing accounts to OpenId. Also some folks may just not want to go the OpenId route. So both are supported on this form.

OpenId validation starts by providing a OpenId Url. The Url depends on the provider and you can pick up that Url from your provider’s OpenId information page – here is a list of a few common ones from the OpenId site. I also provided a couple of icon buttons for the most likely culprits – MyOpenId, Google and Yahoo. Any others need to be typed in. Between Google and Yahoo you probably have an account. There’s also myOpenId which is one of the early providers which is popular, and with it you use a url like http://rickstrahl.myopenid.com to log on. The Yahoo (http://yahoo.com/) and Google (https://www.google.com/accounts/o8/id) Urls are the same for everybody, so those links can actually be auto-fired directly by the click on the icon, while the myOpenId entry requires entering the name first and manually clicking the button.

Once the Login button is clicked (or automatically fired) the process takes you to the provider’s site. For example if you use Google, you’ll see a Google Login screen if you aren’t already signed into Google and you are asked to login. The page will tell you the source site that’s requesting the login and possibly which profile information is requested. Once you log in you are then redirected hence you came – back to the same url where you came from by default. This can be overridden, but in an MVC application you’ll most likely will want to come back to the same URL to redisplay the login (or registration) data with some indication that you are now logged in.

If you are already logged into the OpenId provider (which is usually the case) the login operation is completely transparent – the page is re-routed to the OpenId provider site and immediately returns back to your site at a Url that is specifed. This usually happens pretty fast through a bunch of redirects – from your site to the OpenId provider and fro the OpenId provider back to your site – so other than some extra delay time you don’t even see anything happening. It just looks like you’re going directly to your next page in the app.

When the page returns after a login it includes encoded authentication information on the querystring that becomes available to the request that handles the OpenId login. Specifially DotNetOpenAuth can read the authentication token and any profile information that was returned if any was requested and returned.

Once logged in my profile display then reflects the account association like this:

Loggedin

Figure 2 – Logged in with OpenId and viewing the Profile

The application stores the user’s open id as part of the profile and so it’s easy to display this information. The OpenId relationship can be unlinked and the account can then be assigned to another OpenId or a username and password has to be provided if no OpenId is available. As I mentioned the actual OpenId authentication process is pretty simple but some of the issues surrounding it like attaching and disconnecting accounts from OpenId is a little more complicated – I’ll talk about that below.

If I go to the profile page when the account is not associated with an OpenId the page has an option to login and effectively associate the account with the new OpenId:

Registration

The same page is also used for new registrations which is useful in order to provide the name and email actually from the OpenId provider (as supported – note that google and yahoo do not provide this data in a format that DotnetOpenAuth extracts automatically).

How to implement OpenId in an MVC Application

Please keep in mind that what I describe is just one way you can do this in this case using DotNetOpenAuth since it provides a host of different ways to implement OpenId. There are high level helpers and for Web Forms there is a server control that you can use. In this example I handle this process manually so you can see the flow of the requests. The process is pretty simple although there’s a fair bit of code (most of it boilerplate) that is involved.

I’ll start with the sign in form because that’s simpler. This relates to figure 1 in the image sequence above. The OpenId login area of the page is wrapped into its own HTML form that kicks off the processing. The layout for this block looks like this:

<% using (Html.BeginForm(new { Controller="Account",Action="OpenIdLogon" }))
       { %>         
         <fieldset >                             
         <div class="containercontent" >
             <div class="labelheaderblock">Log on using OpenId:</div>
             <input id="openid_identifier" name="openid_identifier"/>             
             <input id="btnOpenIdLogin" type="submit" value="Login"/>        
             <img id="imgOpenIdLoginProgress" src="<%= ResolveUrl("~/css/images/loading_small.gif") %>" style="display:none" />                 
             
            <div id="divOpenIdIcons">
                 <img src="<%= ResolveUrl("~/images/openid-icon.png") %>" onclick="openIdUrl('openid')" title="myopenid.com" class="hoverbutton" />
                 <img src="<%= ResolveUrl("~/images/google-icon.png") %>" onclick="openIdUrl('google')" title="Google" class="hoverbutton"/>
                 <img src="<%= ResolveUrl("~/images/yahoo-icon.png") %>" onclick="openIdUrl('yahoo')" title="Yahoo" class="hoverbutton" />
             </div>     
         </div>
         </fieldset>
<% } %>   

The image icons fire a small JavaScript routine that creates the OpenId urls that are injected into the text box:

function openIdUrl(site)
{
    var value = "";
    var autoClick = false;
    
    if (site == "openid") {
        value = "<Your Account>.myopenid.com"
    }
    else if (site == "google") {
        value = "https://www.google.com/accounts/o8/id";
        autoClick = true;
    }
    else if (site == "yahoo") {
        value = "http://yahoo.com/"
        autoClick = true;
    }
        
    if (value) {
        var jText = $("#openid_identifier");
       jText.val(value)
            .focus();
       if (autoClick)
           $("#btnOpenIdLogin").trigger("click");
    }       
}

You can add to this with other URLs and icons as you see fit. I stuck with those three because they are the most common that people will have accounts on but that may change. All others OpenId Urls can be typed in by hand (which is not so smooth).

When the Login button is clicked and the request is submitted it fires into an OpenIdLogOn action method of the controller. The controller has to accept both POST and GET operations. POST is from the initial button click, the GET will occur when the OpenId provider fires back the response and provides the data via the query string.

As mentioned I’m using DotNetOpenAuth here which is a single large DLL you can drop into your BIN folder. It handles all the heavy lifting of creating the redirect link and cracking the response query string extracting the authentication token.

The process involves two steps: Making the original request to the OpenId provider (which redirects) and handling the response that comes back. Here’s the code (which is pretty close to the sample code provided in one of the demos):

[AcceptVerbs(HttpVerbs.Post | HttpVerbs.Get), ValidateInput(false)]
public ActionResult OpenIdLogOn(string returnUrl)
{
    var openid = new OpenIdRelyingParty();
    var response = openid.GetResponse();
    if (response == null)  // Initial operation
    {
        // Step 1 - Send the request to the OpenId provider server
        Identifier id;
        if (Identifier.TryParse(Request.Form["openid_identifier"], out id))
        {
            try
            {
                var req = openid.CreateRequest(Request.Form["openid_identifier"]);
                return req.RedirectingResponse.AsActionResult();
            }
            catch (ProtocolException ex)
            {
                // display error by showing original LogOn view
                this.ErrorDisplay.ShowError("Unable to authenticate: " + ex.Message);
                return View("Logon",this.ViewModel);
            }
        }
        else
        {
            // display error by showing original LogOn view
            this.ErrorDisplay.ShowError("Invalid identifier");
            return View("LogOn",this.ViewModel);
        }
    }
    else  // OpenId redirection callback
    {
        // Step 2: OpenID Provider sending assertion response
        switch (response.Status)
        {
            case AuthenticationStatus.Authenticated:
                string identifier = response.ClaimedIdentifier;

                // OpenId lookup fails - Id doesn't exist for login - login first
                if (busUser.ValidateUserOpenIdAndLoad(identifier) == null)
                {
                    this.ErrorDisplay.HtmlEncodeMessage = false;
                    this.ErrorDisplay.ShowError(busUser.ErrorMessage +
                            "Please <a href='" + WebUtils.ResolveUrl("~/Account/Register") +
                            "'>register</a> to create a new account or <a href='" +
                            WebUtils.ResolveUrl("~/Account/Register") +
                            "'>associate</a> an existing account with your OpenId");

                    return View("LogOn", this.ViewModel);
                }

                // Capture user information for AuthTicket
                // and issue Forms Auth token
                UserState userState = new UserState()
                {
                    Email = busUser.Entity.Email,
                    Name = busUser.Entity.Name,
                    UserId = busUser.Entity.Id,
                    IsAdmin = busUser.Entity.IsAdmin
                };
                this.IssueAuthTicket(userState, true);

                if (!string.IsNullOrEmpty(returnUrl))
                    return Redirect(returnUrl);

                return Redirect("~/new");

            case AuthenticationStatus.Canceled:
                this.ErrorDisplay.ShowMessage("Canceled at provider");
                return View("LogOn", this.ViewModel);
            case AuthenticationStatus.Failed:
                this.ErrorDisplay.ShowError(response.Exception.Message);
                return View("LogOn", this.ViewModel);
        }
    }
    return new EmptyResult();
}

The code uses the OpenIdRelyingParty class to handle the processing of the login. The first step occurs when the button is clicked and there’s no provider response to process. This code:

var req = openid.CreateRequest(Request.Form["openid_identifier"]);
return req.RedirectingResponse.AsActionResult();

fires off the the request. The openid_identifier is the OpenId Url the user entered into the form and it’s passed to DotNetOpenAuth to start formulating a request for authentication at that Url.

The code above starts the redirection sequence going to the provider and back to the current page  - since I didn’t specify another Url to return to by default the request returns to the current page’s url.

As mentioned I’m using MVC so and as you can see DotNetOpenAuth conveniently includes an AsActionResult() method that creates redirect response in a proper MVC style response which is very thoughtful for consistency. You can also retrieve the URL directly and fire it on your own although I can’t see why you would. If you’re using WebForms or a handler there are other methods on RedirectingResponse to fire the redirects for you.

Next the browser is redirected to the OpenId provider where you either are asked to log in or if you already are logged in you are authenticated and immediately returned to the current page. When the request returns it is now a GET operation and we start back out at the top of the same Controller action.

This time around:

var response = openid.GetResponse();

will not be null – the return URL includes a bunch of new encoded querystring values. GetResponse() looks for those and based on that creates the response instance and the code can branch into the greater if block. The request will either be authenticated, canceled or failed. Failure can be any number of things such as invalid login information, or timing out or some sort of tampering with the URL. Both cancel and failure operations are handled by simply redisplaying the current view with an error message.

If authentication succeeded we can retrieve a unique identifier that identifies the user’s provider choice and we’re guaranteed at this point that this user was authenticated through OpenId.  Yay!

Although you now know the user is authenticated ASP.NET knows nothing about this authentication yet, so the next step is to set a FormsAuthentication ticket to authenticate the user to the application. This process is no different than doing a manual login in any other ASP.NET application with Forms Authentication.

In CodePaste.net I store a user record in the ticket so I have some basic user information available to my application on each hit without having to use session storage or retrieving this info on every request from the database. The code does this:

 // Capture user information for AuthTicket
// and issue Forms Auth token
UserState userState = new UserState()
{
    Email = busUser.Entity.Email,
    Name = busUser.Entity.Name,
    UserId = busUser.Entity.Id,
    IsAdmin = busUser.Entity.IsAdmin
};
this.IssueAuthTicket(userState, true);

where IssueAuthTicket() looks like this doing standard Forms Authentication ticket assignment:

/// <summary>
/// Issues an authentication issue from a userState instance
/// </summary>
/// <param name="userState"></param>
/// <param name="rememberMe"></param>
private void IssueAuthTicket(UserState userState,bool rememberMe)
{
    FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, userState.UserId,
                                                         DateTime.Now, DateTime.Now.AddDays(10),
                                                         rememberMe, userState.ToString());

    string ticketString = FormsAuthentication.Encrypt(ticket);
    HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, ticketString);
    if (rememberMe)
        cookie.Expires = DateTime.Now.AddDays(10);

    HttpContext.Response.Cookies.Add(cookie);
}

Once the auth ticket’s been issued the application simply redirects to another page in the application (the new snippet page in this case).

BTW just for clarfication, the userState object contains logic to easily persist to and from string so this object is easily retrieved at the beginning of a request and stored on updates. In my base controller I do:

protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
    base.Initialize(requestContext);

    // Grab the user's login information from FormsAuth
    UserState userState = new UserState();
    if (this.User.Identity != null && this.User.Identity is FormsIdentity)
        this.UserState.FromString(((FormsIdentity)this.User.Identity).Ticket.UserData);

    // have to explicitly add this so Master can see untyped value
    this.ViewData["UserState"] = this.UserState;
    this.ViewData["ErrorDisplay"] = this.ErrorDisplay;
}

The process to authenticate using OpenId is pretty straight forward. No surprises here, really. All we need to have is some conditional logic to route for redirect initially and pick up the result when the request comes back and handle the authentication and creation of a new FormsAuthentication ticket. When the request returns and is marked as authenticated you can safely assume the user is valid. Because of the message encryption and timed security tokens the data is safe and not hijackable.

Registration – A little more complex

The log in process is straight forward primarily because the request doesn’t need to track data that is already on the page. You log in and you move on to another page. However in a log in form (Figure 3) there may be additional pieces of information that you need to track. More importantly you typically have some state associated with the registration page – like which user (or a new user) is currently being displayed and that information has to be carried forward when the Open Id request returns from the provider. So the code to handle this is a little longer.

The HTML layout in Figure 3 is nearly identical to the HTML layout on the login form except there’s some conditional logic to display the input box or the checkbox and message that identifies the current OpenId association.

    <% using (Html.BeginForm(new { Controller = "Account", Action = "OpenIdRegistrationLogOn" })){ %>         
         <fieldset >                             
         <div class="containercontent" style="padding: 10px 20px;" >
             
             
            <% if( string.IsNullOrEmpty(Model.busUser.Entity.OpenId) ) { %>
                 <label for="openid_identifier" class="labelheaderblock">Enter an OpenId Url:</label>
                 <input id="openid_identifier" size="40" name="openid_identifier"/>             
                 <input id="btnOpenIdLogin" type="submit" value="Login"/> 
                 <img id="imgOpenIdLoginProgress" src="<%= ResolveUrl("~/css/images/loading_small.gif") %>" style="display:none" />                 
                 
                 <div id="divOpenIdIcons">
                     <img src="<%= ResolveUrl("~/images/openid-icon.png") %>" onclick="openIdUrl('openid')" title="myopenid.com" class="hoverbutton" />
                     <img src="<%= ResolveUrl("~/images/google-icon.png") %>" onclick="openIdUrl('google')" title="Google" class="hoverbutton"/>
                     <img src="<%= ResolveUrl("~/images/yahoo-icon.png") %>" onclick="openIdUrl('yahoo')" title="Yahoo" class="hoverbutton" />
                 </div>
                             
             <% } else { %>    
                 <label for="openid_identifier" class="labelheaderblock">OpenId Association</label>
                 <img src="<%= ResolveUrl("~/css/images/greencheck.gif") %>" /> This user account is linked to <b><%= Html.Encode(Model.busUser.Entity.OpenId) %></b>
                 <input id="btnOpenIdUnlink" name="btnOpenIdUnlink" type="submit" value="Unlink" />                     
             <% } %>

             <%= Html.Hidden("Id2", this.Model.busUser.Entity.Id) %>
         </div>
         </fieldset>
    <% } %>

Notice that here we need to keep track of the active User if any. Since you can attach/detach from an existing profile we do have to know which user we’re dealing with. This sounds easy enough but remember that you can’t use plain POST data for this because the page goes off the OpenId provider and comes back with a GET that just holds the result values. This means to redisplay the page the entire page has to be re-rendered based on the model. To do this we’ll need to keep track of the user id for the request sequence.

This base process is very  similar to the log in process and uses the same two stages of request sending and return. First capture the value from a hidden form variable and then store it in the Session object to pass between the two requests. Here’s what the open id attachment/detachment code looks like:

[AcceptVerbs(HttpVerbs.Post | HttpVerbs.Get), ValidateInput(false)]
 public ActionResult OpenIdRegistrationLogOn(FormCollection formVars)
 {
     return OpenIdRegistrationLogOn(null,true);
 }
 
 private ActionResult OpenIdRegistrationLogOn(IAuthenticationResponse response, bool reserved )
 {
     this.ViewData["IsNew"] = true;

var openid = new OpenIdRelyingParty(); if (response == null) response = openid.GetResponse(); if (response == null) { string userId = Request.Form["Id2"]; // have to track the user’s id // Check for unlink operation if (!string.IsNullOrEmpty(Request.Form["btnOpenIdUnlink"])) { if (busUser.Load(userId) == null) { this.ErrorDisplay.ShowError("Couldn't find associated User: " + busUser.ErrorMessage); return RedirectToAction("Register", new { id=userId }); } busUser.Entity.OpenId = ""; busUser.Save(); return RedirectToAction("Register", new { id = userId }); } Identifier id; string openIdIdentifier = Request.Form["openid_identifier"]; if (Identifier.TryParse(openIdIdentifier, out id)) { try {
// We need to know which user we are working with
// and so we pass the id thru session – (is there a better way?)

Session["userId"] = userId;
var req = openid.CreateRequest(id); var fields = new ClaimsRequest(); fields.Email = DemandLevel.Request; fields.FullName = DemandLevel.Request; req.AddExtension(fields); return req.RedirectingResponse.AsActionResult(); } catch (ProtocolException ex) { this.ErrorDisplay.ShowError("Unable to authenticate: " + ex.Message); this.busUser.NewEntity(); return View("Register",this.ViewModel); } } else { ViewData["Message"] = "Invalid identifier"; return View("Login"); } } else {
// Reestablish the user we’re dealing with
string userId = Session["userId"] as string; if (string.IsNullOrEmpty(userId)) ViewData["IsNew"] = true;
else ViewData["IsNew"] = false; // Stage 3: OpenID Provider sending assertion response switch (response.Status) { case AuthenticationStatus.Authenticated: var claim = response.GetExtension<ClaimsResponse>(); string email = null, fullname= null; if (claim != null) { email = claim.Email; fullname = claim.FullName; } string identifier = response.ClaimedIdentifier; var user = busUser.Load(userId); if (user == null) { user = busUser.ValidateUserOpenIdAndLoad(identifier); if (user == null) user = busUser.NewEntity(); } // associate openid with the user account busUser.Entity.OpenId = identifier; if (!string.IsNullOrEmpty(email) && string.IsNullOrEmpty(busUser.Entity.Email)) busUser.Entity.Email = email; if (!string.IsNullOrEmpty(fullname) && string.IsNullOrEmpty(busUser.Entity.Name)) busUser.Entity.Name = fullname; if (string.IsNullOrEmpty(busUser.Entity.Name)) { string host = StringUtils.ExtractString(response.FriendlyIdentifierForDisplay, ".", ".com"); if (!string.IsNullOrEmpty(host)) host = " (" + host + ")"; busUser.Entity.Name = "Unknown" + host; this.ErrorDisplay.DisplayErrors.Add("Please enter a user name", "Name"); } if (!busUser.Validate()) { busUser.Entity.OpenId = ""; this.ErrorDisplay.ShowError(busUser.ErrorMessage); return View("Register",this.ViewModel); } if (!busUser.Save()) { busUser.Entity.OpenId = ""; this.ErrorDisplay.ShowError(busUser.ErrorMessage); return View("Register", this.ViewModel); } UserState userState = new UserState() { Name = busUser.Entity.Name, Email = busUser.Entity.Name, UserId = busUser.Entity.Id, IsAdmin = busUser.Entity.IsAdmin }; this.IssueAuthTicket(userState, true); // and reload the page with the saved data return this.RedirectToAction("Register", new { id=busUser.Entity.Id } ); case AuthenticationStatus.Canceled: this.busUser.NewEntity(true); this.ErrorDisplay.ShowMessage("Canceled at provider"); return View("Register", this.ViewModel); case AuthenticationStatus.Failed: this.busUser.NewEntity(true); this.ErrorDisplay.ShowError(response.Exception.Message); return View("Register", this.ViewModel); } } return new EmptyResult(); }
 

There are two methods here – one as an actual endpoint and another that can be called with an existing response passed to it – this is useful to automatically force a login that doesn’t exist directly into the registration form via code. A DotNetAuth response can only be parsed once or else it’s considered invalid and so passing in an existing response was a requirement to handle this sort of ‘manual’ routing.  For the simple callback scenario you can just use a simple Controller endpoint method.

As in the last example the OpenIdRelyingParty is used to handle the actual request semantics of sending the redirect and receiving the data. This part of the process really doesn’t change. However, how you deal with the data received and abort scenarios adds a significant amount of additional code compared to the previous login only example.

If there’s no OpenId response, the code first checks for unlink requests. For this it needs the id to load an instance of the business entity and update it by removing the OpenId from it. The page is then redisplayed again using the ID as the key.

Otherwise the request is sent off to the OpenId provider. The provider then returns and again the OpenId response will now be set. On success the code tries to retrieve the ClaimsRequest values. Claimsrequest is sent with the initial request to ask the server to return values from the user’s profile. For CodePaste.net I’m interested in full name and email as optional results. Note that some OpenId only providers use a common format called SREG to return values. myOpenDns does as do several other of the dedicated services. However, this standards is only sketchily supported by the big providers. Google for example requires that you Demand data and then will only return the email address (which seems ironic given that’s the most private item in the profile). Yahoo doesn’t return anything – according to their OpenId page they only share profile information with certain sites they deem appropriate. SREG however is gaining momentum so it’s likely that all providers will evenutally support SREG.

In DotnetOpenAuth the request for ‘claim data’ is handled like this:

// Request additional Profile
var req = openid.CreateRequest(Request.Form["openid_identifier"]); information with ClaimsRequest
var fields = new ClaimsRequest();
fields.Email = DemandLevel.Demand;
fields.FullName = DemandLevel.Demand;
req.AddExtension(fields);

You can Request or Demand claims. In theory Demand means that if the provider or user refuses to provide the requested keys the request fails. In reality though providers like google ignore Request commands and so Demand is often required to retrieve information.

To retrieve the claims data uses GetExtension<T>():

// Retrieve custom profile information if available
var claim = response.GetExtension<ClaimsResponse>();
string email = null, fullname= null;
if (claim != null)
{
  email = claim.Email;
  fullname = claim.FullName;
}

Just remember that there’s no guarantee these values are set.

The other issue in this controller deals with state management of the user. When the user comes to the page to hook up we have state on the page for that particular user, but when we re-direct to the OpenId provider site we loose that state. So in this case the user id needs to be tracked and I’m using a Session object for this.  I haven’t had much need for session in MVC applications but this is one case where I don’t see a way around this unfortunately. Before the request is sent the user’s id is stored in a Session object. When the authenticated request returns the session id is retrieved and used to look up the user’s business object which is the updated with the open id identifier and optionally the profile information if any. The rest of the bulky code deals with a few checks on the data returned and isolating which data to update.

OpenId Integration – More complicated than it looks

Phew – this looks like a lot of code you have to write and while it’s a pretty good chunk the largest part of this code has to do with the business logic to save and update the user’s information and setting the Forms Authentication ticket. The actual OpenId related logic is relatively simple once you understand the basic concept of how the flow of an OpenId authentication works.

As any Web based callback mechanism the code is bulky because you have to effectively route the responses inside your code and you end up with some code that couples Web and business logic which is always ugly. I remember fighting similar issues years ago with PayPal integration.  Processes like these are cumbersome because they are so closely tied to the actual Web request – the Controller Action in this case and don’t provide for an easy way to abstract and wrap into business logic. That’s the penalty you pay for a distributed system in many situations and this is no different.

There are also other solution out there for OpenId integration. Rob Conery has been mucking around and trying to convince me to try RPX which uses a man in the middle approach for hosting OpenId authentication. They basically provide the login API via a pop up window and you then retrieve the authentication data via a backend service call. But this requires yet another provider in the middle that you talk to through a proprietary API. It might help isolating the auth logic (service callback vs. a controller action). But it’s not for me – but for some of you this might be interesting to check out especially if you like the way the RPX looks. It'll definitely be a less code approach.

I hope though that this article is useful to some of you. I certainly wish I would have had something to look at when I started looking at OpenId.There’s a lot of high level posturing stuff out there that talks about the benefits and goals of open ID but preciously little code that actually shows the progression of steps in context that explains OpenId flow on a high level along with an implementation.

I also want to thank

Resources

  • DotNetOpenAuth Library
    .NET library for processing OpenId, oAuth, InfoCard and various other authentication APIs. Very comprehensive and thoughtful functionality although documentation is a bit on the sparse side. Excellent support and feedback from the developer who’s really passionate about this library and closely monitors and listens to feedback.
  • CodePaste.net Source Code
    This article is based on the code on CodePaste.net and if you like you can check out the source code from the Subversion repository.
Make Donation
Posted in ASP.NET  MVC  


Feedback for this Post

 
# Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by DotNetKicks.com September 17, 2009 @ 9:18pm
You've been kicked (a good thing) - Trackback from DotNetKicks.com
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by William Kapke September 18, 2009 @ 12:09am
There is definitely a lack of useful information out there. Thanks for getting this ASP.NET walk through together.

I've been hoping someone would put together a well made video of the process outlining the TECHNICAL details. Such as an animated flow of the data.

Isn't there a spec out there that is trying push using "username@myhost.com" instead of "httt://username.myhost.com"? I believe it works by first sending a request out to myhost.com and getting a url pattern back- which it then injects the username into. (I wish I knew where I read about that)

I think using the email pattern will help with adoption.
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Rick Strahl September 18, 2009 @ 1:23am
@William - I'm not sure that a video walk through would be all that helpful - this is the sort of thing where you need some code to look at and copy and paste and a fair bit of it.

As to the urls - they are actually fairly simple for most providers. They are URLs because those are unique whereas email addresses are flakey that way especially with email addresses changing. It looks like most providers are going the route of using a simple URL only. Yahoo is just Yahoo.com and they figure out to route to the openid provider based on the query string data coming in. I see really no benefit of having any sort of user identification with the URL - if you're not logged in you have to provide your credentials anyway and it makes the url easier to work with if it's the same for all users. Once you're logged in with an OpenId provider (which if you use it is most of the time they can pull up your profile before you log on and auto-fill whatever non-private information on the sign on form.

The simpler the better and the simplest is the same URL for everyone for each provider.
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Raxit September 18, 2009 @ 3:38am
Thanks Rick for sharing this. One thing I am not clear with google open id. I want to display User E Mail once the user successfully gets authenticated and return to my web site very similar to what you have done but it returns tokenized URL like www.google.com/accounts/o8/id?id=AItOawmtAnQvSXGhRTkAHZ... when I access e.Response.FriendlyIdentifierForDisplay .

Upon searching for a while I got this thread http://stackoverflow.com/questions/1355292/friendly-name-from-google-using-openid which says google does not return friendly Identifiers. Let me know if I am missing something here.

FYI: I am using controls provided with DotnetOpenAuth i.e OpenIdLogin, OpenIdButton.
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Andrew Arnott September 18, 2009 @ 5:52am
Hi Rick,

First of all, thanks for your post. That's probably the most detailed and thought out one I've seen yet.

But you have a small (big) problem. You're using IAuthenticationResponse.FriendlyIdentifierForDisplay to make security decisions. It should ONLY be used to display the identifier to the user (thus the long name ;). You should be using IAuthenticationRespones.ClaimedIdentifier as the forms auth ticket username to keep your site secure against identity spoofing attacks.
# Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth - Rick Strahl
by DotNetShoutout September 18, 2009 @ 7:01am
Thank you for submitting this cool story - Trackback from DotNetShoutout
# Внедряем OpenID в приложение ASP.NET MVC
by progg.ru September 18, 2009 @ 9:57am
Thank you for submitting this cool story - Trackback from progg.ru
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Rick Strahl September 18, 2009 @ 11:51am
@Raxit - The 'friendly identifier' is determined by the OpenId provider and you have no control over what that is. Google returns a long and nasty string for this which is fine - you don't really need to display it anywhere (i just did because it was a showcase more or less). Ideally the real information that you want to store/keep/hold on to is profile information but as I pointed out in the article this information is of mixed quality and not guaranteed to be provided.

FriendlyIdentifier in the code above is what DotnetOpenAuth returns. I believe if something is missing it'll just return the unique url returned which is required as part of the OpenId conversation. Some other sites like myopenid.net return name.myopennet.id as the friendly identifier which is nicer for sure.
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Rick Strahl September 18, 2009 @ 11:58am
@Andrew - thanks for the feedback. Not sure I follow though. I'm storing the friendly id in the profile only as a lookup which is ONLY checked after a AuthenticationStatus.Authenticated callback that should be guaranteed to be secure (otherwise your library wouldn't return this code and it would be pointless, right? :-}).

The forms authentication ticket includes *nothing* at all related to OpenId. I basically only authenticate through OpenId and once I'm done I issue a totally unrelated forms authentication ticket that has no direct connection to the OpenId identifier or anything else for that matter. Plain FormsAuth ticket.

Since the only way authentication occurs is through the AuthenticationStatus.Authenticated callback I don't see how this could be insecure. The friendly Id is merely used as the lookup key that maps the OpenId account and the user record on my site.

Using the ticket retrieved from OpenId wouldn't work in this scenario because that ticket will change with each login - so no continuity. The way it is now if the Forms Auth ticket expires or the user logs out, next time they have re-authenticate with OpenId and go through the same assignment cycle again.

If you see a hole here please could you clarify, because I don't see it?
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Mike September 18, 2009 @ 1:57pm
Rick
You CAN get email info from google...you have to enable this in the web.config and add as an extension on your request:
request.AddExtension(new ClaimsRequest
{
Email = DemandLevel.Require,
});
It's documented on the dotnetopenauth site pretty well and I just implemented this for MonoRail easily.
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Andrew Arnott September 18, 2009 @ 2:01pm
Rick,

You are creating the auth ticket using the OpenID "friendly identifier":

...new FormsAuthenticationTicket(1, userState.Name...

This, and the fact that you use the friendly identifier for lookup is what's wrong. You MUST use ClaimedIdentifier for username lookup, which in turn means you really should use it for the forms auth ticket's username as well. The FriendlyIdentifierForDisplay is NOT guaranteed to be unique for each user as the OpenID Claimed Identifier is. For instance, https://somebody.com and http://somebody.com are by OpenID standards two DIFFERENT people, and yet FriendlyIdentifierForDisplay will be "somebody.com" for both of them. That's why you must use ClaimedIdentifier, which includes the full, unique string of the user's OpenID whenever you look up "ok, so this guy is logging in, but where's his user record and data?"

And since forms authentication in ASP.NET makes HttpContext.Current.User.Name so easy to get and make security decisions on, you should use ClaimedIdentifier when creating the forms auth ticket there as well. FriendlyIdentifierForDisplay should only be used when you're emitting HTML to the browser so you can say "Welcome, somebody.com"! Since that's easier on the eyes than "Welcome, https://somebody.com/". That's all the friendly identifier is for.

The ClaimedIdentifier is NOT rotated at each login so that the user would have to go through the assignment cycle. It's a constant for a user across all logins, so it's a perfect (and the only secure) way to look up a user in your database.

I hope this helps.
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Rick Strahl September 18, 2009 @ 2:09pm
@Mike - I know you can get email from Google, but nothing else. You cannot get fullname or any other profile items at least not currently.
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Rick Strahl September 18, 2009 @ 7:56pm
@Andrew thanks for the clarification. I've changed the code above and on the site (along with some logic to update ids currently in there if necessary - looks like several of the providers have the claimed id and friendly Id actually being the same thing though.

Just for clarification though - I'm not using the OpenId in my AuthTicket because not everyone is using open auth. Rather the user id is used (thanks you also found a bug there - I was using the name not the id). I still allow plain username/password logins and looking at the site most new signups still use those rather than OpenId. I have less than 10% of users using the OpenId currently. <shrug>

Thanks for looking this over, the feedback and of course DotNetOpenAuth. It's made the actual login portion very easy.
# Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth - Rick Strahl's Web Log
by DotNetShoutout September 21, 2009 @ 2:53pm
Thank you for submitting this cool story - Trackback from DotNetShoutout
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Jarrett Vance September 22, 2009 @ 1:14pm
I've also done some work on OpenID in ASP.NET MVC. However, I took a somewhat oldschool approach and created an OpenIdAuthenticationModule that could be plugged in side-by-side with the FormsAuthenticationModule that already exists.

Some work still needs to be done to request a user name from people using Google OpenId as, for now, it just uses "OpenID user" as their nick name.

You can see the source code @ http://code.google.com/p/atomsite/source/browse/trunk/OpenIdPlugin/OpenIdAuthenticationModule.cs
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Rick Strahl September 23, 2009 @ 7:03pm
@Jarret - like that approach too and might actually be a little more reusable around the application. Cool, thanks for posting the link.
# #.think.in infoDose #43 (11th September - 22nd September)
by #.think.in October 02, 2009 @ 6:43am
#.think.in infoDose #43 (11th September - 22nd September)
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Benjamin Robbins October 04, 2009 @ 12:09am
I've recently switched from RPX to DotNetOpenAuth in a personal project. I'm very happy with DotNetOpenAuth's capabilities, but if I needed simple Facebook or MySpace integration, then I'd go with RPX.

As an alternative to using Session to store your state, you can use IAuthenticationRequest.AddCallbackArguments(string key, string value) during stage 1 before you send the initial OpenID request and IAuthenticationResponse.GetCallbackArgument(string key) after you receive the authenticated response from the OpenID provider during stage 2. These methods add and retrieve your state from the query string that is passed around during the OpenID magic dance. In this specific case, I don't think using Session would be that big of a deal, but if you were in a situation where using Session was painful, then storing your state in the query string becomes much more useful.
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Germán October 05, 2009 @ 3:11am
Hi!

Great, great sample. In order to avoid such a long method I have splitted it in two actions with an ActionMethodSelectorAttribute so there's no need of a a great if code block.

Thanks. Germán.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using DotNetOpenAuth.OpenId.RelyingParty;
using System.Reflection;

namespace Sherezade.Web.Filters
{
    public class OpenIdAuthenticationCallbackAttribute : ActionMethodSelectorAttribute
    {        
        public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
        {
            var openid = new OpenIdRelyingParty();
            var response = openid.GetResponse();                     

            // We do have an openId response, it's the provider calling back
            if (response != null)
            {
                // look for IAuthenticationResponse parameter and pass the response object to the action as we can't call GetResponse() twice later in the action method
                var parameterName = methodInfo.GetParameters().Where(pi => pi.ParameterType.Equals(typeof(IAuthenticationResponse))).Select(pi => pi.Name).SingleOrDefault();
                if(!String.IsNullOrEmpty(parameterName))
                {
                    controllerContext.RouteData.Values.Add(parameterName, response);
                }
                return true;
            }
            return false;
        }
    }
}


        // handles openid form submission from user
        public ActionResult OpenIdLogin()
        {
            var openid = new OpenIdRelyingParty();            
            Identifier id;            
            if (Identifier.TryParse(Request.Form["openidIdentifier"], out id))
            {
                try
                {
                    var req = openid.CreateRequest(Request.Form["openidIdentifier"]);
                    var fields = new ClaimsRequest();
                    fields.Email = DemandLevel.Require;
                    req.AddExtension(fields);
                    return req.RedirectingResponse.AsActionResult();
                }
                catch (ProtocolException ex)
                {
                    return View("Registro", null);
                }
            }
            else
            {
                return View("Registro", null);
            }
        }
        // handles the provider callback
        [OpenIdAuthenticationCallback]
        public ActionResult OpenIdLogin(IAuthenticationResponse response)
        {
            switch (response.Status)
            {
                case AuthenticationStatus.Authenticated:
                    break;
                case AuthenticationStatus.Canceled:
                    break;
                case AuthenticationStatus.Failed:
                    break;
            }

            return new EmptyResult();
        }
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Mahdi Taghizadeh October 13, 2009 @ 4:37pm
Thank you for your great post!

It seems that when OpenID provider returns user back to the page it doesn't append ReturnUrl which is added by FormsAuthentication mechanism and so I couldn't redirect user to the page he had come from. In fact input value 'returnUrl' is only accessible in first request to our Action not the second request to our action which is made by OpenID provider.

Any idea?
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Rick Strahl October 13, 2009 @ 5:34pm
@Mahdi - you can set the return URL or alternately you can cache it and then explicitly redirect to it. I don't think you can really return back to another page effectively unless you want to also put the Authorization code into that other handler. I think you have to come back to the same page in order to isolate the auth operation. Once auth'd you can then decide where to go with a Redirect if necessary.

You should know where you want Forms Auth to go when you start the process and you can use a Session var (or better DNOA's version thereof) to hold the original return url and then go back to that.
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by .Net Freelancer October 14, 2009 @ 7:33am
Thanks for the good explanation and the code snippets. It had make things really easy for me in implementing OpenID in one of my projects.
# OpenId in a .NET MVC Application
by Ross's Blog November 09, 2009 @ 5:16am
OpenId in a .NET MVC Application
# OpenId request-response
by free bird December 17, 2009 @ 6:37pm
U hav mentioned that each OpenID provider uses a different request-response format. Is it possible to know the exact format and encoding in which they do it, especially Google and Yahoo...
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Hadi Hariri January 11, 2010 @ 1:36am
Hi Rick,

A few suggestions I'd like to make in regard to MVC, is that you should try and avoid returning HTML from a controller. Let the view handle that. Same goes for actual messages. Also the actions seem a little too long.
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Tuna Toksoz January 11, 2010 @ 12:59pm
Agree with Hadi Hariri. It is a presentation concern and should be handled in the view. Controller actions seems fatty, perhaps you can move those into something like authenticationprovider or service.
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Rick Strahl January 11, 2010 @ 2:06pm
Hadi & Tuna - which parts are you talking about when you say that? The header/redirects? The error display?

With either of those I don't agree. The former is an operational task and the behavior (the redirects) is implicit in the operation (authentication) so IMHO that's totally justified for controller code. The error message text is applied to an ErrorDisplay object that is part of the ViewData Model and output as part of the main display.
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Johnnyo February 21, 2010 @ 2:30am
Hi Rick,

Thanks for this great post. I have a question about a potential security issue in your figure 3 code. You have the following mark-up, and if I understand correctly, the hidden Id2 field is used to authenticate and identify the user (after it's stored in the session).

<%= Html.Hidden("Id2", this.Model.busUser.Entity.Id) %>

If someone is able to guess someone else's userId, then wouldn't that person easily be able to login as another person by simply changing the value of the hidden field?

Thanks,
Johnny
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Rick Strahl February 21, 2010 @ 3:06pm
@Johnny - you'd still want to validate the retrieved Id vs. your openId login. If you look at the code that goes with that routine you see that it checks this value only once a Response is returned from the OpenID provider - IOW only if there was a valid login that occurred. The ID is merely a value that's passed forward for storage later, not for validation on its own.
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Anıl Çalışkan March 29, 2010 @ 6:23am
Thanks for the post Rick, I have a little question:

if (this.User.Identity != null && this.User.Identity is FormsIdentity)
this.UserState.FromString(((FormsIdentity)this.User.Identity).Ticket.UserData);

For the above snippet, FromString is a method implemented by you? After the "userState.ToString()" in IssueAuthTicket, ticket's UserData won't be the "X.Y.Models.Userstate"? I couldn't get how do you store and reuse the UserData.

Thanks
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Rick Strahl March 29, 2010 @ 2:44pm
@Anil - yes. The UserState object has an internal serialization/deserialization mechanism that I use to store the object values in the FormsAuth ticket. It's basically a string concat - real simple. It works well to persist user data without having to look it up each time w/o overhead of full serialization.
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by amir zada March 31, 2010 @ 4:38am
Great article.
there is much information about dotnetopenauth implementation. but i m stuck in the code
return req.RedirectingResponse.AsActionResult();
this return an error message of assembly missing system.web.mvc 1.0.0.0 and i inform u that i m using mvc 2.0.0.0 and all the thing going until this line of code executed.

Hoping of quick response.
thanks
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Daniel April 30, 2010 @ 8:00am
Hi. I'm trying to integrate openid dotnet in my site, but I have an isue due to bug
http://dotnetopenauth.net:8000/ticket/163.
Can you indicate a solution, like an old version, without this bug?
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by James Clarke May 12, 2010 @ 6:18am
Rick.. are you aware of something similar but for Windows Live ID?

thx

James
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Ian August 04, 2010 @ 7:31pm
What is busUser? When I copy/paste into my project, it says busUser is undefined.
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Goran Gligorin November 14, 2010 @ 2:35pm
Hi!

How exactly do you store UserState as string to the FromsAuthenticationTicket? I'd like to make a similar class myself but can to seem to figure this out.

Thanks,
Goran
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Rick Strahl November 14, 2010 @ 5:32pm
I've done this quite a bit for certain light weight serialization schemes. Create ToString() and FromString() methods that handle the serialization/deserialization mechanism simply.

http://codepaste.net/ShowUrl?url=http%3A%2F%2Fwww.west-wind.com%3A8080%2Fsvn%2Fcodepaste%2Ftrunk%2FCodePasteMvc%2FClasses%2FUserState.cs&Language=C%23

You could also alternately serialize into JSON or XML without the manual code, but the manual code is faster and smaller in size.
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Arnab C February 23, 2011 @ 3:44am
Nice,
Now if I could just find an example where you could get your google and yahoo contacts in asp.net mvc similar to yours.
# re: Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
by Shyju May 19, 2012 @ 8:50am
Is there any workaround if the server time is behind UTC time, My code is returning an error saying the the "The message expirefd at XXXX time and the server time is XXXXX. Obviously because the Server time is behind the UTC time. My hosting (shared) is not ready to change their server time.
 


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2014