If you are not yet familiar with BrowserID, you can read up on it here: https://browserid.org/ There's a nice little Youtube video explanation, along with plenty of documentation.
As a new BrowserID user, you confirm your email one time via an emailed confirmation
link. After that, you can sign in to any BrowserID-enabled site with only your
verified email address. No need to store any passwords! This is more natural
to users than say, OpenID. It's also a lot easier for the developer.
There are two components to implementing BrowserID in ASP.NET:
1) Some Client script that you put in your login page (or UserControl, if you like)
2) A .NET class that will take the encrypted assertion that comes back to the browser,
and verify it. The most important item that this returns (server side) is the
authenticated user's email address. You now know that this is a verified
email, that the user is who they say they are, and so there is no need for clumsy
sign-up forms or passwords to remember (or that need to be stored in a a database which subjects you to security issues).
Here is sample code for the browser script:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Login.ascx.cs"
Inherits="BrowserID.Net.Login1" %>
<script src="https://browserid.org/include.js" type="text/javascript"></script>
<script type="text/javascript">
<!--
function browserID() {
navigator.id.getVerifiedEmail(function (assertion) {
if (assertion) {
document.getElementById('browserid_assertion').value = assertion;
document.getElementById('browserid_form').submit();
} else {
alert('Could not complete identification');
}
});
return false;
}
-->
</script>
<div align="center">
<input type="hidden" name="assertion" value="" id="browserid_assertion"
/>
<asp:ImageButton id=imgSubmit runat=server ImageUrl="images/sign_in_blue.png"
/>
<asp:Label ID=lblMessage runat="server" />
</div>
And my code for the verification class:
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Web;
using Newtonsoft.Json;
namespace BrowserID.Net
{
public class BrowserId
{
public string Audience { get; set; }
public string Assertion { get; set; }
public string Email { get; set; }
public string Validity { get; set; }
public string Issuer { get; set; }
private string PostRequest(string url, NameValueCollection data)
{
WebClient wc = new WebClient();
byte[] b = wc.UploadValues(url, data);
string s = System.Text.Encoding.UTF8.GetString(b);
return s;
}
public BrowserId(string audience, string assertion)
{
this.Audience = audience;
this.Assertion = assertion;
}
/*
Send the assertion to the browserid.org server (this must be over HTTPS)
The response is read to determine if the assertion is authentic
*/
public bool VerifyAssertion()
{
NameValueCollection parameters = new NameValueCollection();
parameters.Add("assertion", this.Assertion);
parameters.Add("audience", this.Audience);
string s=PostRequest("https://browserid.org/verify", parameters);
JsonData obj= JsonConvert.DeserializeObject<JsonData >(s);
if( obj.status=="okay")
{
this.Email = obj.email ;
this.Validity = obj.validity;
this.Issuer = obj.issuer;
return true;
}
else
{
return false;
}
}
}
public class JsonData
{
public string email { get; set; }
public string status { get; set; }
public string validity { get; set; }
public string issuer { get; set; }
}
}
I use NewtonSoft.JSON to handle the JSON ->JsonData class deserialization
That's it! That's all it takes. And here is the codebehind class for my
UserControl:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace BrowserID.Net
{
public partial class Login1 : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
Page.Form.Attributes.Add("onSubmit" , "return browserID();");
if (lblMessage.Text == "" && Request["assertion"] != null)
{
string assertion = Request["assertion"];
var audience = Request.Url.Host + ":" + Request.Url.Port.ToString();
var browID = new BrowserID.Net.BrowserId(audience, assertion);
if (browID.VerifyAssertion())
{
var users = Membership.FindUsersByEmail(browID.Email);
if (users.Count > 0) // yes this is a valid user, log them in
{
FormsAuthentication.SetAuthCookie(browID.Email, true, "/");
FormsAuthentication.RedirectFromLoginPage(browID.Email, true, "/");
}
else
{
Response.Redirect("~/CreateUser.aspx");
}
}
else
{
lblMessage.Text = "Identification failure";
}
}
}
}
}
Note that I am using the user's Email address for their username in the Membership tables. There is no need for a password, so I just re-use the email address for that in the CreateUser method:
protected void CreateUserWizard1_ContinueButtonClick(object sender, EventArgs e)
{
if (!Roles.RoleExists("user"))
Roles.CreateRole("user");
Roles.AddUserToRole(CreateUserWizard1.Email, "user");
FormsAuthentication.RedirectFromLoginPage(CreateUserWizard1.Email, true);
}
Is that super-easy, or what!
The configuration of the web.config should look very familiar:
<connectionStrings>
<remove name="LocalSqlServer"/>
<add name="LocalSqlServer" connectionString="server=(local);database=test;Integrated Security=SSPI" providerName="System.Data.SqlClient" />
</connectionStrings>
<system.web>
<authentication mode="Forms">
<forms loginUrl="~/Login.aspx" defaultUrl="~/Default.aspx" cookieless="UseCookies" enableCrossAppRedirects="true" path="/" />
</authentication>
<anonymousIdentification enabled="true" cookieTimeout="30" cookieName=".anon" />
<membership defaultProvider="SqlProvider" userIsOnlineTimeWindow="20" >
<providers>
<add connectionStringName="LocalSqlServer" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false"
passwordFormat="Hashed" applicationName="/" minRequiredPasswordLength="1" requiresUniqueEmail="true" minRequiredNonalphanumericCharacters="0"
name="SqlProvider" type="System.Web.Security.SqlMembershipProvider" />
</providers>
</membership>
<roleManager enabled="true" defaultProvider="SqlProvider">
<providers>
<clear />
<add connectionStringName="LocalSqlServer" applicationName="/" name="SqlProvider"
type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a" />
</providers>
</roleManager>
In my sample ASP.NET Web Application I've provided four pages and one user control:
1) Install.aspx - this configures your "TEST" database for Membership,
Roles, and Profiles. You need to create the database in SQL Server first.
2) ControlTest.aspx - this is the host page for the user control. It has no code.
3) Login.ascx - The BrowserId UserControl to drop on any page.
4) Default.aspx - the default page which has a LoginName control on it.
5) CreateUser.aspx - A page to create a new user, with a CreateUserWizard control
on it.
Everything else is quite "Plain Vanilla". With this ammunition, you are
all ready to start using BrowserID with FormsAuthentication, Membership, Roles
and Profile.
You can download the working Visual Studio 2010 solution here.