6812.pngIn the previous post we’ve got dead simple OpenID authentication working with ASP.NET WebForms application. Lets push it further and make more real world example you can actually use in your project. To make it less work, we will use Javascript OpenID selector – handy little library to display providers and handle user input. It is just two small scripts and one CSS file, very light and easy to modify if needed.

First we have to modify login.aspx to include markup required by OpenID selector. It is straight HTML with IDs that JavaScript included in OpenID selector will use to manipulate elements on the page.

<fieldset>
    <legend>Login using OpenID</legend>
    <div class="openid_choice">	
        <p>Please click your account provider:</p>	
        <div id="openid_btns"></div>
    </div>
    <div id="openid_input_area">
        <input type="text" id="openid_identifier" />
        <input type="submit" value="Log On" />
    </div>
    <asp:Button runat="server" Style="display: none" ID="btnProvider" Text="" OnClick="Btn_Provider_Click" />
    <asp:HiddenField runat="server" ID="hdnProvider" Value="" ViewStateMode="Enabled" />
    <noscript>	
        <p>OpenID is service that allows you to log-on to many different websites
            using a single indentity. Find out <a href="http://openid.net/what/">
            more about OpenID</a>and <a href="http://openid.net/get/">
            how to get an OpenID enabled account</a>.
        </p>
    </noscript>
    <div class="oid-msg">
        <asp:Literal ID="litOpenIdMsg" runat="server"></asp:Literal>
    </div>
</fieldset>

On page load, we should see images for supported OpenID providers all generated by that little selector library.

image_thumb_62.png

Normally OpenID selector uses form.submit to call back-end code, but with webforms it does not work as well as with MVC. So I made a small change to selector script to call button click instead. When visitor clicks any of the images, button click event handler will redirect to appropriate provider.

// login.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
    litOpenIdMsg.Text = "";
    litOpenIdMsg.Visible = false;
    var openid = new OpenIdRelyingParty();
    var response = openid.GetResponse();
    if (response != null)
    {
        switch (response.Status)
        {
            case AuthenticationStatus.Authenticated:
                var id = StringToGUID(response.ClaimedIdentifier.ToString());
                foreach (MembershipUser u in Membership.GetAllUsers())
                {
                    Guid uid = (Guid)u.ProviderUserKey;
                    if (uid == id)
                    {
                        FormsAuthentication.RedirectFromLoginPage(u.UserName, false);
                        HttpContext.Current.Response.Redirect("../Default.aspx");
                    }
                }
                Session["Authentication-id"] = id;
                HttpContext.Current.Response.Redirect("Register.aspx");
                break;
            case AuthenticationStatus.Canceled:
                litOpenIdMsg.Text = "Canceled at provider";
                litOpenIdMsg.Visible = true;
                break;
            case AuthenticationStatus.Failed:
                litOpenIdMsg.Text = response.Exception.Message;
                litOpenIdMsg.Visible = true;
                break;
        }
    }
}

protected void Btn_Provider_Click(object sender, EventArgs e)
{
    try
    {
        var url = this.hdnProvider.Value;
        using (OpenIdRelyingParty openid = new OpenIdRelyingParty())
        {
            var request = openid.CreateRequest(url); request.RedirectToProvider();
        }
    }
    catch (Exception ex)
    {
        litOpenIdMsg.Text = "Authentication error: " + ex.Message;
        litOpenIdMsg.Visible = true;
    }
}

public Guid StringToGUID(string value)
{
    var md5Hasher = MD5.Create();
    var data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(value));
    return new Guid(data);
}

If visitor not currently logged into the provider service, like any Google service or Yahoo or any other provider he choses, then normal login process will take place. Once logged in, provider, in this case Google, will pass authentication cookie back to our login.aspx from where it was called.

image_thumb_63.png

We could’ve ask provider to supply information needed to programmatically register account without asking user to enter any data. This is actually would be a preferred way of doing things, but this approach has its issues. Even when field marked as required, provider may refuse to give it away and we would have to handle multiple cases with logic getting complex and ugly pretty fast. More reliable ask user to provide only essential information, for example name and email, and use it instead of hoping that provider will return it to us. This only requires to register account, all subsequent logins we won’t ask it again.

image_thumb_64.png

When login page called back by OpenID provider and authentication is confirmed, we check if user with this authentication ID exists in the membership database. If yes – all set, otherwise we save authentication ID to session variable and redirect to register page. Register page is much simplified and only has name and email fields. On create user click, we add this new user to membership using ID as both user ID and password. Because it has to be a GUID, we use small helper function to convert authentication ID into GUID.

    // register.aspx.cs
    protected void CreateUserButton_Click(object sender, EventArgs e)
    {
      MembershipCreateStatus status;var id = Session["Authentication-id"];
      Membership.CreateUser(UserName.Text, id.ToString(), Email.Text, null, null, true,		 id, out status);
      if (status == MembershipCreateStatus.Success)
      {	
        FormsAuthentication.RedirectFromLoginPage(UserName.Text, false);	
        HttpContext.Current.Response.Redirect("../Default.aspx");
      }
      else
      {	
        ErrorMessage.Text = "Error regestering user";
      }
    }

As new user successfully created, we redirect back to application and this new user account as good as if user went through usual create account process.

image_thumb_65.png

Now it is all about how your application wants to handle it, you can set roles, security permissions, add profile etc. The benefit is your users can login using accounts they won’t forget and it won’t make any difference in a way you handle those accounts in the membership. It is still pretty simple, with a nice user experience. The complete project is attached and you can build and run it with VS 2010.

Updated solution file:

WebApp2.zip (753.1KB)