Toolroom Tech Blog

Devlopers Digest

Windows authentication with Forms fallback in ASP

Windows authentication with Forms authentication if first one fails. Works with ASP.net and MVC.

These days I was working on a simple MVC 4 application to be hosted on IIS 7.5/8, nothing special. But it made me crazy when I tried to implement the last feature ... windows authentication with forms authentication fallback. THe problem is, that IIS typically supports either Windows or Forms authentication. In principle you can do two things: a single-app solution, where both authentication methods are combined within a single application or a multi-app solution that uses one app per authentication.

How does Windows authentication work?

Windows authentication follows the NTLM/Kerberos challange handshake protocol. This is more or less, what happens:

1) User requests access to a resource

2) The server negotiates the protocol with the client

  • IIS sends two WWW-Authenticate headers: Negotiate and NTLM

3) Our negotiation comes up with NTLM authentication (Kerberos is more secure/complex, search it on your favorite search engine):

  • IIS responds with a 16-byte random challenge message
  • The client sends a hash, generated from the users password to encrypt the challenge sent by the server
  • IIS now sends user name, the original challenge, and the response from the client to the Domain Controller
  • The DC obtains the password hash for the user, and then uses this hash to encrypt the original challenge. Next, the domain controller compares the encrypted challenge with the response from the client computer. If they match, the domain controller sends the server confirmation that the user is authenticated.
  • If the credentials are ok, IIS grants access to the requested resource
  • IIS passes to ASP.NET the token that represents the authenticated user or anonymous user account. It is accessible via HttpContext.User.

Single-App Solutions

I found lots of suggestions to create a single-application solution, but mostly not worth implementing it due complexity or possible maintenance issues when moving the application to other servers.

Microsofts Paul Wilson writes on http://msdn.microsoft.com/en-us/library/ms972958.aspx how to do it with classic ASP.net on IIS 6. His solution builds up on forms authentication and requires two login pages and a custom 401 page. But on the one hand i do not want no modify the application too much andf on the other hand it's hard to make it work on IIS7.

So, I decided to use a multi-application approach that indeed needs a second application but is much smarter and cleaner in my opinion.

Multi-App Solution

As already said, you can only use either windows or forms authentication in a asp.net application. And this is exactly the way this solution is based on.

Pros

  • No modification or optimization of your base application
  • Use only standard authentication modules
  • Attach this solution to any forms application

Contras

  • Requires a second application or at least a sub-application on IIS

Implementation Summary

First of all we need our base web application providing configured to use forms authentication. Then we need another, almost empty web application, that is configured to use Windows Authentication. The latter is responsible to do the Windows Authentication and to set the forms authentication cookie before it redirects to our base web application. If the windows authentication did not work, HttpContext.User.Identity.IsAuthenticated is set to false and the user will be redirected to the forms login page.

How to do it

ASP.net uses a so called machine key for ViewState encryption and validation as well for signing the authentication ticket for forms authentication. This feature was introduced especially for load balancing. That means, if both of our applications, the base and the authentication app, use the same key, they can both access the authentication cookie.

In our authentication app we just need one Action, I create the action 'Index' within a controller named 'NtlmController'. Since the Windows authentication of the request is already done before the action is executed, we can  access the authentication token here. If the token says, the user is authenticated, we set the authentication cookie and that's it.

public ActionResult Index()
{
	var principal = (IPrincipal) Thread.CurrentPrincipal;
	if (principal != null) {
		if (!principal.Identity.IsAuthenticated)
			throw new ArgumentException("Principal is not authenticated, should not happen :)");
		//User is validated, so let's set the authentication cookie
		FormsAuthentication.SetAuthCookie(principal.Identity.Name, true);
	}

	//use this if your authentication app is a sub-app of your base app
	//return Redirect("~/../");

	//use this if your authentication app is another app on the server
	return Redirect("http://www.mywinauthloginapp.com");
}

Now generate a machine key for the authentication app. In IIS >=7 it's easy to achieve this: In IIS left click on your app, in the features view open machine key and click generate keys. Save it and IIS will update your web.config automatically.

 

The resulting line should look like this:

<system.web>
	...
	<machineKey decryptionKey="C7976FD237C42579DDD645F4649DD05A3F09F32069691C34" validation="SHA1" validationKey="1EE24CF0741E7F7557E5390CCEF5F5DF1BF90F098294AF2BC3D8B3C2F1195E2E268377FDD7361AE8998374064A6CB23DE361414DDDDA90DF49D4BC6E55687F63" />
	...
</system.web>

Now let's do the only modifications to our base app. First copy the machineKey line to to the web.config and add some parameters to the forms authentication configuration, so that redirects between both applications are allowed:

<system.web>
	...
	<machineKey decryptionKey="C7976FD237C42579DDD645F4649DD05A3F09F32069691C34" validation="SHA1" validationKey="1EE24CF0741E7F7557E5390CCEF5F5DF1BF90F098294AF2BC3D8B3C2F1195E2E268377FDD7361AE8998374064A6CB23DE361414DDDDA90DF49D4BC6E55687F63" />
	<authentication mode="Forms">
		<forms loginUrl="/Account/Login" timeout="2880" domain="localhost" path="/" enableCrossAppRedirects="true" name=".ASPXFORMSAUTH" protection="All" />
    </authentication>
	...
</system.web>

That's it. If you directly access your base app, you'll get your forms login. But if you access the authentication app from a machine within your domain, you will automatically be logged on.

Good luck.

 

 

Furher information

http://msdn.microsoft.com/en-us/library/aa292114(VS.71).aspx
http://msdn.microsoft.com/en-us/library/ff647076.aspx
http://msdn.microsoft.com/en-us/library/ms972958.aspx

AJAX templating with JQuery and MVC

Example how to add AJAX templating automation with ASP MVC and JQuery.

AJAX Templating with ASP.net MVC and JQuery

In principle, when using dynamically created web pages, i.e. by Asp or PHP, the web server creates the HTML, sends it to the client, and the client renders the whole stuff.

Now you might want to load additional content without refreshing the whole page. You can do this with simple callbacks to the server, where the server again produces some HTML and sends it back to the server. This means, that lots of HTML is sent. Such asynchronous calls should need the shortest roundtrip time as possible, since users wait for content. And, believe me, users cannot wait.

Templating is a great way to gain performance in asynchronous server interaction with AJAX. Therefore the server produces a invisible template on the initial response. Then, instead of sending HTML, the server just provides us with plain data.

In the following example I load a table with data from a log file. When the user clicks a refresh button and/or a timer triggers a refresh function, we get new data.

Step One: Create base HTML

Based on the LogItem collection we create a simple table in the index file that may contain initial static data.

@model JQueryTemplating.Models.LogItemCollection
<table id="logItemData" style="width:100%;">
    <thead>
        <tr>
            <th>
                @Html.LabelFor(l => fakeLogItem.TimeStamp)
            </th>
            <th>
                @Html.LabelFor(l => fakeLogItem.Title)
            </th>
            <th>
                @Html.LabelFor(l => fakeLogItem.Type)
            </th>
            <th>
                @Html.LabelFor(l => fakeLogItem.Description)
            </th>
        </tr>
    </thead>
    <tbody>
        @Html.DisplayFor(l => l)
    </tbody>
</table>

For initial rows we need a DisplayTemplate, that goes into the DisplayTemplates folder.

@model JQueryTemplating.Models.LogItem
<tr class="line@(Model.Type)">
    <td>
        @Html.DisplayFor(i => i.TimeStampStr)
    </td>
    <td>
        @Html.DisplayFor(i => i.Title)
    </td>
    <td>
        @Html.DisplayFor(i => i.Type)
    </td>
    <td>
        @Html.DisplayFor(i => i.Description)
    </td>
</tr> 

Step Two: Add Attribute and Extension 

Now I do some Voodoo to allow model driven templating and Razor support. Therefore I first add an annotation to markup a models property for templating inclusion.

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class TemplateAttribute : Attribute
{
	public string Value { get; set; }

	private string _displayFormat = "{{{0}}}";
	public string DisplayFormat
	{
		get { return _displayFormat; }
		set { _displayFormat = value; }
	}
}

Then I add an extension method to the MvcHtmlString in order to access the template values i.e. with Razor.

public static MvcHtmlString TemplateValueFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
	var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

	var prop = metaData.ContainerType.GetProperty(metaData.PropertyName);
	if (prop == null)
		return null;

	var templateAttribute = prop.GetCustomAttributes(typeof(TemplateAttribute), false).FirstOrDefault() as TemplateAttribute;
	if (templateAttribute == null)
		return null;

	var result = string.Format(templateAttribute.DisplayFormat, templateAttribute.Value jQuery1520833310639602945_1349033015193 metaData.PropertyName);
	return new MvcHtmlString(result);
} 

Step Three: Add HTML Template for AJAX

Now, let's add Ajax stuff. Therefore, in we create a template row within the document that contains the table. To have valid HTML, i put it into a separate table. In principle the row is equal to the DisplayTemplate.

<table id="logItemTemplate" style="display:none;">
    <tbody>
        <tr class="line@(Html.TemplateValueFor(i => i.Template.Type))">
            <td>
                @Html.TemplateValueFor(i => i.Template.TimeStampStr)
            </td>
            <td>
                @Html.TemplateValueFor(i => i.Template.Title)
            </td>
            <td>
                @Html.TemplateValueFor(i => i.Template.Type)
            </td>
            <td>
                @Html.TemplateValueFor(i => i.Template.Description)
            </td>
        </tr>
    </tbody>
</table>

 

Step Four: Add Action that provides data

Now we need an MVC Action to call for new data. Optionally we can provide it with a timestamp that defines the time of the latest item we have already received, so that we only get new log entries.

public class DataController : Controller
{
	//
	// GET: /Data/
	[HttpPost]
	public ActionResult Get(DateTime? timestamp)
	{
		var repository = new LogItemRepository();
		var items = repository.GetData(timestamp);

		return Json(items);
	}
}

Step Four: Add AJAX functionality

Finally, let's add the jquery part for asynchronous updates and a button to enable/disable those updates.

$(function () {
        //Attach to the click event of the start/stop button
        $('#start').click(function () { start(); });

        //Used for the delegate function for periodic calls
        var refreshDelegate = false;

        //AJAX function that fetches new data from the server
        var refresh = function () {
            $.ajax({
                url: '/Data/Get',
                type: 'POST',
                dataType: 'json',
                success: function (data) { handleData(data); },
                error: function () {
                    clearInterval(refreshDelegate);
                    if (confirm('Connection refused. Please click ok to try it again.')) { start(); };
                },
                cache: false
            });
        };

        //handles the JSON response
        function handleData(data) {
            //Iterate throught all items
            $.each(data, function () {
                //Clone the Template
                var html = $('#logItemTemplate>tbody').clone().html();
                //Iterate through the key/value pairs
                $.each(this, function (k, v) {
                    if (v != null && v.indexOf('/Date(') == 0) {
                        v = convertToDate(v);
                    }
                    //Replace the key by its corresponding value
                    var regex = new RegExp('{' + k + '}', "g");
                    html = html.replace(regex, v);
                });

                //Finally prepend the processed row to the table
                $('#logItemData>tbody').prepend(html);
            });
        }

        //Helper function to convert the serialized .net datetime format
        function convertToDate(str) {
            var re = /-?\d+/;
            var m = re.exec(str);
            return new Date(parseInt(m[0]));
        }

        //Sets or stops the periodic timer
        function start() {
            if (refreshDelegate == false) {
                refreshDelegate = setInterval(refresh, 1000);
            } else {
                clearInterval(refreshDelegate);
                refreshDelegate = false;
            }
        }
    });

Download sample solution 

JQueryTemplating.zip (2.35 mb)

Returning Templates after AJAX Request in MVC 3

How to return a DisplayTemplate or EditorTemplate in a partial response after AJAX calls in ASP.net MVC 3.

Ever had the need to return a DisplayTemplate or EditorTemplate within a Response after an AJAX call? Altough it is not very nice, the easiest way seems to be this ... and it may save lots of time: just access the view directly by its filename.

public PartialViewResult GetNew()
{
	return PartialView(
		"~/Views/Receipe/EditorTemplates/IngredientModel.cshtml", 
		new IngredientModel { Id = Guid.Empty }
	);
}