ASP.NET: Integrating BrowserId and ASP.NET Membership With Your MVC Application

BrowserID is a distributed system that allows users to use their email address as the login name and password. It is much simpler than OpenID, and also much easier for developers to integrate into their web sites. There is also no need to store passwords or password hashes, since all authentication is performed by the offsite provider.

I've been what you could call a "late convert" to the MVC congregation, but after version 3 and the Razor engine came out I felt it was sufficiently mature for me to jump on board.   I've written about this subject before  for classic WebForms with ASP.NET. This article covers how to do it with ASP.NET MVC.

In this app, all the client-side business logic is done in the LogOnPartial.cshtml Razor shared View via jQuery calls:

@{
    var loginCssClass = Request.IsAuthenticated ? "hide" : "show";
    var loggedViewCssClass = Request.IsAuthenticated ? "show" : "hide";
}

<div id="login" class="@loginCssClass"></div>
<div id="loggedinview" class="@loggedViewCssClass">
<div id="gravatar"></div>

| <a id="logout" href="#logout">Logout</a>
</div>
<script language="javascript">
    $(document).ready(function () {

        $('#login').click(function () {
            navigator.id.getVerifiedEmail(gotVerifiedEmail);
        });

        $('#logout').click(function (e) {
            e.preventDefault();
            $.ajax({
                type: 'POST',
                url: '/Account/LogOff',
                data: null,
                success: function (res, status, xhr) {
                    logout();
                    window.location.href = res;
                }
            });
        });

        if (navigator.id && navigator.id.sessions.length > 0) {
            setDisplayStyle(navigator.id.sessions);
        }
    });
    
    function setSession(val) {
        if (navigator.id) {
            navigator.id.sessions = val ? val : [];
        }
    }

    
    function gotVerifiedEmail(assertion) {
       if (assertion !== null) {
            $.ajax({
                type: 'POST',
                url: '/Account/LogOn',
                data: { assertion: assertion },
                success: function (res, status, xhr) {
                    if (res != null) {
                        loggedIn(res.email);
                    }
                },
                error: function (res, status, xhr) {
                    alert("login failure" + res);
                }
            });
        }
    }

    function loggedIn(email) {
        setSession(email);
        $('#login').toggleClass().removeClass('show').addClass('hide');
        $('#loggedinview').removeClass('hide').addClass('show');
        setDisplayStyle(email);
    }
    
    function setDisplayStyle(email) {
        var iurl = 'http://www.gravatar.com/avatar/' + Crypto.MD5($.trim(email).toLowerCase()) + "?s=20";
        $("<img>").attr('src', iurl).appendTo($("#gravatar"));
         $("<span>").html(email).appendTo($("#gravatar"));
    }

    function  logout() {
        setSession();
        $('#login').removeClass('hide').addClass('show');
        $('#loggedinview').removeClass('show').addClass('hide');
        $('#gravatar').html('');
    }
</script>


When the Sign-in image button is clicked, a call is made to the BrowserId javascript include's "getVerifiedEmail" function, which receives an assertion, passing it in the results of a POST to the Account/LogOn Controller.  That code looks like this:

using System;
using System.Web.Mvc;
using System.Web.Security;
using Demo;

namespace Demo.Controllers
{
    public class AccountController : Controller
    {
         public ActionResult LogOn()
        {
            return View();
        }

         [HttpPost]
         public ActionResult LogOn(string assertion)
        {
            var authentication = new BrowserIDAuthentication();
            var verificationResult = authentication.Verify(assertion);
             if (verificationResult.IsVerified)
            {
                string email = verificationResult.Email;
                 var users = Membership.FindUsersByEmail(email);
                 if (users.Count > 0) // yes this is a valid user, log them in
                 {
                     FormsAuthentication.SetAuthCookie(email, true, "/");
                 }

                 return Json(new { email });
             }

             return Json(null);
        }

         [HttpPost]
         public ActionResult LogOff()
        {
            FormsAuthentication.SignOut();
            return Json(FormsAuthentication.LoginUrl);
        }
    }
}

It takes the assertion returned by the getVerifiedEmail client script method and sends it into a server-side Verify method:

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using Demo;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Demo
{
    /// <summary>
    /// Class that wraps the BrowserID authentication mechanism.
    /// </summary>
    public class BrowserIDAuthentication
    {
         /// <summary>
        /// Gets or sets the identity authority verification URL.
        /// </summary>
        public Uri VerificationUri { get; private set; }

        /// <summary>
        /// Initializes a new instance of the BrowserIDAuthentication class.
        /// By default it uses <c>https://browserid.org/verify</c>
        /// </summary>
        public BrowserIDAuthentication()  
         {
             this.VerificationUri = new Uri("https://browserid.org/verify");
            
        }

         /// <summary>
        /// Initializes a new instance of the BrowserIDAuthentication class.
        /// </summary>
        /// <param name="verificationUrl">The identity authority verification URL.</param>
        public BrowserIDAuthentication(string verificationUrl)
         {
             this.VerificationUri = new Uri(verificationUrl);
        }

        /// <summary>
        /// Veriy the assertion supplied by the browser.
        /// </summary>
        /// <param name="assertion">The assertion data to verify.</param>
        /// <returns>Instance of VerificationResult</returns>
        public VerificationResult Verify(string assertion)
        {
            JObject verificationResult = this.VerifyAssertion(assertion);
            string email = "";
            string status = "";
            string audience = "";
            string issuer = "";
            bool verified = false;
            if (verificationResult != null && verificationResult["status"].ToString() == "okay")
            {
                email = verificationResult["email"].ToString();
                status = verificationResult["status"].ToString();
                audience = verificationResult["audience"].ToString();
                issuer = verificationResult["issuer"].ToString();

                verified = true;
            }
            return new VerificationResult {Email = email, IsVerified = verified,Audience =audience , Issuer=issuer  };
        }

      
         private JObject VerifyAssertion(string assertion)
        {
            NameValueCollection parameters = new NameValueCollection();
             parameters.Add("assertion", assertion);
              var audience = HttpContext.Current.Request.Url.Host + ":" + HttpContext.Current.Request.Url.Port.ToString();
             parameters.Add("audience", audience);
            string s=PostRequest("https://browserid.org/verify", parameters);
             var obj = JObject.Parse(s);
             return obj;
        }

         private string PostRequest(string url, NameValueCollection data)
        {
            WebClient wc = new WebClient();
            byte[] b = wc.UploadValues(url, data);
        wc.Dispose();
            string s = System.Text.Encoding.UTF8.GetString(b);
             return s;
        }
    }
}

The VerifyAsertion method makes a POST to the BrowserId.org/Verify service, passing the assertion string that was returned to the browser, and an "audience" parameter that consists of the Host and Port of the requesting page. The NewtonSoft.JSON JObject that is created from parsing the returned string is then used to populate a VerificationResult instance:

using System.Web;
using Demo;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Demo
{
    /// <summary>
    /// Class that wraps the BrowserID authentication mechanism.
    /// </summary>
    public class BrowserIDAuthentication
    {
         /// <summary>
        /// Gets or sets the identity authority verification URL.
        /// </summary>
        public Uri VerificationUri { get; private set; }

        /// <summary>
        /// Initializes a new instance of the BrowserIDAuthentication class.
        /// By default it uses <c>https://browserid.org/verify</c>
        /// </summary>
        public BrowserIDAuthentication()  
         {
             this.VerificationUri = new Uri("https://browserid.org/verify");
            
        }

         /// <summary>
        /// Initializes a new instance of the BrowserIDAuthentication class.
        /// </summary>
        /// <param name="verificationUrl">The identity authority verification URL.</param>
        public BrowserIDAuthentication(string verificationUrl)
         {
             this.VerificationUri = new Uri(verificationUrl);
        }

        /// <summary>
        /// Veriy the assertion supplied by the browser.
        /// </summary>
        /// <param name="assertion">The assertion data to verify.</param>
        /// <returns>Instance of VerificationResult</returns>
        public VerificationResult Verify(string assertion)
        {
            JObject verificationResult = this.VerifyAssertion(assertion);
            string email = "";
            string status = "";
            string audience = "";
            string issuer = "";
            bool verified = false;
            if (verificationResult != null && verificationResult["status"].ToString() == "okay")
            {
                email = verificationResult["email"].ToString();
                status = verificationResult["status"].ToString();
                audience = verificationResult["audience"].ToString();
                issuer = verificationResult["issuer"].ToString();

                verified = true;
            }
            return new VerificationResult {Email = email, IsVerified = verified,Audience =audience , Issuer=issuer  };
        }

      
         private JObject VerifyAssertion(string assertion)
        {
            NameValueCollection parameters = new NameValueCollection();
             parameters.Add("assertion", assertion);
              var audience = HttpContext.Current.Request.Url.Host + ":" + HttpContext.Current.Request.Url.Port.ToString();
             parameters.Add("audience", audience);
            string s=PostRequest("https://browserid.org/verify", parameters);
             var obj = JObject.Parse(s);
             return obj;
        }

         private string PostRequest(string url, NameValueCollection data)
        {
            WebClient wc = new WebClient();
            byte[] b = wc.UploadValues(url, data);
        wc.Dispose();
            string s = System.Text.Encoding.UTF8.GetString(b);
             return s;
        }
    }
}

If the email is successfully verified, we use the Membership.FindUsersByEmail method to determine if the user is in our ASP.NET membership database. If so, we set a new FormsAuthentication cookie and return the JSONified email back to the page, where it is bound to the View. There is also code that obtains and displays the user's Gravatar image, if they have one.

if (verificationResult.IsVerified)
            {
                string email = verificationResult.Email;
                 var users = Membership.FindUsersByEmail(email);
                 if (users.Count > 0) // yes this is a valid user, log them in
                 {
                     FormsAuthentication.SetAuthCookie(email, true, "/");
                 }

                 return Json(new { email });
            }

If we don't find the user's email in our Membership database, we can simply redirect them to the standard Create Account form and then log them in from there (using the model.Email, not the username), since we already know their email is verified. Note: This application uses a database "ASPNETDB" which has been attached to SQL Server (not a SQLEXPRESS Connection). You can add or modify users for testing using the standard ASP.NET Administration application. If you are using SQLEXPRESS, you can use any MDF database file you want, provided it has been enabled for Membership, and simply change the connection string in your web.config file. I generally try to avoid publishing demos that rely on SQLEXPRESS as I have seen that it often causes a lot of problems for developers who try to use it in deployed applications.

You can download the complete ASP.NET MVC 3.0 Visual Studio 2010 solution here.

By Peter Bromberg   Popularity  (10316 Views)