Sunday, April 13, 2008

Add OpenID login support to your ASP.NET MVC site

While this post assumes a C# web site, the steps will work for VB.NET just as well, but the syntax of the glue code will have to be adjusted slightly.

Because ASP.NET MVC changes frequently, this will be a high-level outline of what you must do to support OpenID, and the implementation details I will leave up to you and your particular version of MVC.  At the end I will show the code that makes the whole thing work as of the most recent version of MVC at the time of writing.

  1. Download DotNetOpenId and extract the DotNetOpenId.dll found in the bin directory of the download.
  2. Add a reference to that assembly from your ASP.NET MVC web site.
  3. Create or open the controller that will login users.  I will assume this controller is called "UserController".  Create two actions: Login and Authenticate.
    1. Login will just display the Login view.
    2. Authenticate will respond to two requests: the form submission of your visitor who is logging in, and the OpenID Provider response after the redirect.
  4. Your controller should look something like this:
    public class UserController : Controller {
        public void Login() {
            // Stage 1: display login form to user
            RenderView("Login");
        }
        public void Authenticate() {
            var openid = new OpenIdRelyingParty();
            if (openid.Response == null) {
                // Stage 2: user submitting Identifier
                openid.CreateRequest(Request.Form["openid_identifier"]).RedirectToProvider();
            } else {
                // Stage 3: OpenID Provider sending assertion response
                switch (openid.Response.Status) {
                    case AuthenticationStatus.Authenticated:
                        FormsAuthentication.RedirectFromLoginPage(openid.Response.ClaimedIdentifier, false);
                        break;
                    case AuthenticationStatus.Canceled:
                        ViewData["Message"] = "Canceled at provider";
                        RenderView("Login");
                        break;
                    case AuthenticationStatus.Failed:
                        ViewData["Message"] = openid.Response.Exception.Message;
                        RenderView("Login");
                        break;
                }
            }
        }
    }
  5. Create just one view: for the Login action you created.  The Authenticate action does not need a view as it will only redirect the user to other URLs. The content of it should resemble this:
    <% if (ViewData["Message"] != null) { %>
    <div style="border: solid 1px red">
        <%= Html.Encode(ViewData["Message"].ToString())%>
    </div>
    <% } %>
    <p>You must log in before entering the Members Area: </p>
    <form action="Authenticate?ReturnUrl=<%=HttpUtility.UrlEncode(Request.QueryString["ReturnUrl"]) %>" method="post">
    <label for="openid_identifier">OpenID: </label>
    <input id="openid_identifier" name="openid_identifier" size="40" />
    <input type="submit" value="Login" />
    </form>
  6. Adjust FormsAuthentication to use your Login action.
    1. Open your Web.config file.
    2. Find your <authentication> tag.
    3. Set the defaultUrl and loginUrl attributes of the <forms> child item:
      <authentication mode="Forms">
          <forms defaultUrl="/Home" loginUrl="/User/Login"/>
      </authentication>

And that's pretty much it.  Of course you'll want to customize the code to fit what you're specifically trying to do, but this code is all you need to get OpenId authentication going.  I might add: getting OpenId authentication going securely, since there seems to be a rash recently of "cheap" OpenID implementations out there that try to implement just part of the spec and end up with insecure implementations.  This method uses the DotNetOpenId library, which has had a lot more effort to a secure and complete implementation of OpenId 1.x and 2.0.

By the way, I'm new to the MVC framework, so if you have comments on how this code can be made better or prettier, I'm all ears.

[22Apr08 Update: Check out a follow-up post that builds on this sample to pull registration data from the OpenID Provider]

8 comments:

  1. Anonymous1:02 PM

    Hi,

    Instead of being redirected to a "user page" upon authentication, how to be redirected to another page?

    I mean that my user requests a protected page. Since it is protected, he is redirected to the login page (which receives also returnUrl in the request so that it can later be redirected to the original requested page). When the openid response arrives, how to be redirected to the original protected page?

    Thanks
    Nicolas

    ReplyDelete
  2. Anonymous7:13 AM

    @ Nicolas, that will work fine.

    instead of

    case AuthenticationStatus.Authenticated:
    FormsAuthentication.RedirectFromLoginPage(openid.Response.ClaimedIdentifier, false);
    break;

    just have a normal mvc redirect all controller call.

    ReplyDelete
  3. Anonymous7:16 AM

    @ Andrew:

    with your MVC example, it shows how to programmatically use the API to authenticate. i've tried it and it works great :)

    In the visual studio examples for the official download, the relay provider example ALSO requests some personal information located with the identity provider.

    can u blog about how we can extend your MVC example to also request their email, country, etc.. just like the reply provider sample, please?

    cheers :)

    ReplyDelete
  4. Anonymous7:17 AM

    @ Andrew (again):

    if u try to authenticate with some random openID account (eg. askahdskfhsdkfsdkfhksdhfsdhk.openid.com) the following (error) is thrown ????


    DotNetOpenId.OpenIdException: No OpenId endpoint found

    :(

    ReplyDelete
  5. Regarding the OpenIdException, that's true. My sample probably should have included that wrapping the CreateRequest method call in a try/catch block for OpenIdException and handling failures such as this is a good idea.

    ReplyDelete
  6. Hi anon,

    Check out a follow-up post where I add the Simple Registration extension to the sample.

    ReplyDelete
  7. Hi Avdrew..

    Very nice article..
    i have two questions

    how can i give nerddinner like look at login panel http://www.nerddinner.com/Account/LogOn?returnUrl=%2F
    2Iit show whole url after login.how can i show user name only..

    ReplyDelete
  8. Hi @Krishna,
    OpenIDs don't have "usernames". But for display purposes you can show the email address (perhaps just the alias of it) to the user as their username. But it's vital that you don't use that as a username for security purposes.

    ReplyDelete