Safely Displaying Html Formatted User Content

Html escaping in ASP.NET MVC is a great way to prevent Cross-Site Scripting (XSS) attacks on your application.  The only problem I found was that sometimes an application needs to display formatted text and using the “Html.Encode(content)” will remove all formatting (as it is expected to).

So I have written a little extension method that enables specific html formatting to be allowed while still escaping potentally dangerous html.

public static class HtmlHelperExtensions
{
     private const string htmlTag = @"<{0}>";

     public static string HtmlEncode(this HtmlHelper helper, string text)
     {
          // encode the string
          string encodedText = HttpUtility.HtmlEncode(text);

          // put the text in a string builder
          StringBuilder formattedEncodedText = new StringBuilder(encodedText);

          // replace the escaped characters with the correct strings to allow formatting
          // <p>
          formattedEncodedText.Replace(string.Format(htmlTag, @"p"), @"<p>");
          // </p>
          formattedEncodedText.Replace(string.Format(htmlTag, @"/p"), @"</p>");

          // <strong>
          formattedEncodedText.Replace(string.Format(htmlTag, @"strong"), @"<strong>");
          // </strong>
          formattedEncodedText.Replace(string.Format(htmlTag, @"/strong"), @"</strong>");

          // <em>
          formattedEncodedText.Replace(string.Format(htmlTag, @"em"), @"<em>");
          // </em>
          formattedEncodedText.Replace(string.Format(htmlTag, @"/em"), @"<em>");

          // <span style="text-decoration:underline;">
          string underline = @"<span style="text-decoration: underline;">";
          string underlineReplacement = @"<span style=""text-decoration:underline;"">";
          formattedEncodedText.Replace(underline, underlineReplacement);

          // </span>
          // only find the spans that are related to the span for underlining
          var temp = formattedEncodedText.ToString();
          // for each instance of underline
          foreach (int i in temp.IndexOfAll(underlineReplacement))
          {
               // find the first instance of </span> after the underline span and replace
               var index = temp.IndexOf(string.Format(htmlTag, @"/span"), i);

               // delete the string at that location
               temp = temp.Remove(index, string.Format(htmlTag, @"/span").Length);

               // add in the new string at that location
               temp = temp.Insert(index, @"</span>");
          }

          formattedEncodedText = new StringBuilder(temp);

          // <ul>
          formattedEncodedText.Replace(string.Format(htmlTag, @"ul"), @"<ul>");
          // </ul>
          formattedEncodedText.Replace(string.Format(htmlTag, @"/ul"), @"</ul>");

          // <ol>
          formattedEncodedText.Replace(string.Format(htmlTag, @"ol"), @"<ol>");
          // </ol>
          formattedEncodedText.Replace(string.Format(htmlTag, @"/ol"), @"</ol>");

          // <li>
         formattedEncodedText.Replace(string.Format(htmlTag, @"li"), @"<li>");
         // </li>
         formattedEncodedText.Replace(string.Format(htmlTag, @"/li"), @"</li>");

         formattedEncodedText.Replace(@" ", @" ");

         return formattedEncodedText.ToString();
     }
}

Shortening Open Id & Asp.net MVC

I’ve been working on a project that uses Open Id as the authentication method for public users.  I decided to use the DotNetOpenAuth provider to handle a lot of the grunt work for authentication.  The provider does a lot to simplify the process, but I wanted to make it shorter so I wrote a wrapper around the DotNetOpenAuth code.

You can find the example provided by DotNetOpenAuth for Asp.net MVC here. I am using version 3.4.1, so things may change but hopefully not in a newer version.

Requesting Authentication from OpenId Provider

To submit a request to authenticate all you need is the below code.  The first line builds a claims request to ask the Open Id provider for some pieces of information.  In my example below I am only asking for the user’s email address.  The second line handles making the request and redirecting the user to the provider for authentication.

var claimsRequest = OpenIdHelper.CreateClaimsRequest(OpenIdHelper.RequestInformation.Email);
returnOpenIdHelper.Login(openid_identifier, claimsRequest);

Validating Authentication from OpenId Provider

To validate a request is even easier,  the OpenIdHelper has a ValidateResponse function that will return true if the user is valid or false if not.  If the user is valid the oepnIdUser parameter will be populated with the user’s information.  If there was an error the message parameter will contain the error message to do with what you please.

Authentication.OpenIdUser openIdUser;
string message;

if (OpenIdHelper.ValidateResponse(out openIdUser, out message))
{
    // do some work
}

The below class is what is returned in the first parameter.

public class OpenIdUser
{
 /// <summary>
 /// Essentially the userid
 /// </summary>
 public string ClaimedIdentifier { get; set; }

 public DateTime? Birthdate { get; set; }
 public string Country { get; set; }
 public string Email { get; set; }
 public string FullName { get; set; }
 public string Gender { get; set; }
 public string Language { get; set; }
 public string Nickname { get; set; }
 public string PostalCode { get; set; }
 public string TimeZone { get; set; }
}

Putting it together I have the following actions in my account controller.

[AcceptPost]
 public ActionResult Authenticate(string openid_identifier)
 {
 var claimsRequest = OpenIdHelper.CreateClaimsRequest(OpenIdHelper.RequestInformation.Email);
 return OpenIdHelper.Login(openid_identifier, claimsRequest);
 }

 /// <summary>
 /// Response from OpenId provider telling is user is authentic or not
 /// </summary>
 /// <param name="returnUrl"></param>
 /// <returns></returns>
 [ValidateInput(false)]
 public ActionResult Authenticate()
 {
 Authentication.OpenIdUser openIdUser;
 string message;

 if (OpenIdHelper.ValidateResponse(out openIdUser, out message))
     {
          // do some work
     }
 }

My OpenIdHelper can be downloaded at http://gist.github.com/323195

OpenId Provider Selector

It’s really a matter of preference but I picked the OpenId Selector.  I chose it because it’s simple to use.


<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
 LogOn
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="HeaderContent"  runat="server">
 <link href="../../Content/openid.css" rel="stylesheet"  type="text/css" />
 <script src="../../Scripts/openid-jquery.js"  type="text/javascript"></script>
 <script type="text/javascript">
 $(document).ready(function() {
 openid.init('openid_identifier');
 });
 </script>
 </asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

<!-- Simple OpenID Selector -->
<% using (Html.BeginForm("Authenticate", "Account", FormMethod.Post, new { @id = "openid_form" }))
 { %>

 <input type="hidden" name="action" value="verify" />

 <fieldset>
 <legend>Sign-in or Create New Account</legend>

 <div id="openid_choice">
 <p>Please click your account provider:</p>
 <div id="openid_btns"></div>
 </div>

 <div id="openid_input_area">
 <input id="openid_identifier" name="openid_identifier" type="text" value="http://" />
 <input id="openid_submit" type="submit" value="Sign-In"/>
 </div>
 <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>
 </fieldset>

<% } %>

</asp:Content>

It could just be me, but I had a strange problem with the plug-in and had to make a minor change to get it to function properly.  In the situation where I have previously selected an OpenId provider, the next time I come to the page and try to select a different provider it still goes to the original provider that was selected.

In the openid-jquery.js plug-in, I had to change line 169 in the setOpenIdUrl function from :

hidden.value = url;

to

$(hidden).val(url);

Well that’s all I got for my first post, hope it’s at least somewhat useful to someone other than me.  Any feedback or suggestions would be appreciated.

In the openid-jquery.js plug-in, I had to change line 169 in the setOpenIdUrl function from :