Saturday, August 29, 2009

ASP.Net, IIS and SQL Server – Integrated Security, Authentication and Impersonation

In one of my last projects, I was responsible for designing an enterprise wide ASP.Net application that would run on IIS and use integration windows authentication and connect to backend SQL Server database using integrated security i.e. SSPI. As you see, as per the requirement, everything (all network resource access) had to use integrated windows security mechanism i.e. “trusted connection”.
This is indeed a very common development scenario that we face on a regular basis when working with ASP.Net applications.  In order to use Integrated Security, we simply check the ‘Integrated Windows NT Authentication’ (challenge/response) option in IIS, set impersonate=true’ in our web.config file and we are ready to go.

Or are we?
A very common stumbling block that developers face with the above setup is when trying to connect to SQL Server, you end up getting one of the two following error message:
  • Login failed for user '(null)'. Reason: Not associated with a trusted SQL Server connection.
  • Microsoft OLE DB Provider for ODBC Drivers (0x80040E4D)
    [Microsoft][ODBC SQL Server Driver][SQL Server]Login failed for user '\'.
 What’s going on? The reason for the above error is due to the ‘anonymous access’ option also being turned on (checked) in IIS. When this is the case, anonymous access takes precedence over Windows NT Authentication access and user’s credentials are not passed. Ok so what can we do? Let’s turn off ‘anonymous access’ option in IIS and try again to connect to SQL Server. Now you will likely end up getting the following error messages:
  • Microsoft OLE DB Provider for ODBC Drivers error '80040e4d'
    [Microsoft][ODBC SQL Server Driver][SQL Server]Login failed for user 'NT AUTHORITY\ANONYMOUS LOGON'.
 Huh?? The reason is because of a 'double hop' that authentication mechanism undertakes. When the client authenticates with IIS, it passes the logged in user’s (in the domain) NTLM credentials (username/password). This is the ‘first hop’. After IIS has authenticated the user’s credentials now it is time to access SQL Server (or any other secured network resources). But IIS does not pass the NTLM credentials to the SQL Server machine because that would constitute a "second hop" which is not allowed for security reasons.  Therefore trying to access a SQL Server instance running on another machine other than the web server will result in a logon failure error.
So what is the solution to the problem? Two most commonly used solutions are as follows:

  • SQL Server authentication
SQL Server authentication relies on the internal user list maintained by the SQL Server computer. This list does not include Windows NT users, and is specific to the SQL Server computer. You need to provide a specific SQL Server user account’s “username” and “password” in the connection string when connecting to SQL server.


  • Windows NT authentication
This is what Microsoft’s Tech Net article has to say - “To configure IIS for Windows NT authentication, you cannot use Windows NT Challenge\Response (NTLM) authentication”. This essentially means do not connect to SQL Server using a trusted connection from ASP.Net application when using Windows NT authentication with IIS. Instead do the following:
  • Use ‘Basic Authentication’
  • Use anonymous access and follow steps to setup proper authentication (refer the article http://support.microsoft.com/kb/247931/)
  • Use Windows NT Authentication and use a specific “generic domain account” (ensure it has been given appropriate access and level of permission to the SQL database). Then use either of the following two ways to provide the generic user credential:
    • Specify a username and a password in the connection string of the (this is similar to SQL Server authentication)
    • Specify values in the impersonation settings and still use a trusted connection. You can also encrypt the web.config to protect the username/password information



 Now that we have seen the approaches to solve the “double hop” problem next comes the question “Can we impersonate an account at runtime, programmatically?” e.g. say I have the following 2 accounts: (i) DOMAIN\User1 and (ii) DOMAIN\User2 where User1 has “write access” while User2 has ‘full access’ to SQL Server (or any other resource). Once I am done with the network resource I want to continue accessing with the original logged in user’s credentials. In other words, I want to be able to execute certain code under a specific impersonated user credential. Is this possible?

Yes, absolutely. Here is the article on MSDN that explains it all. How To: Use Impersonation and Delegation in ASP.NET 2.0. It explains all the various Impersonation Scenarios namely:
  • Impersonating the original caller. You want to access Windows resources that are protected with ACLs configured for your application's domain user accounts.
  • Impersonating the original caller programmatically. You want to access resources predominantly by using the application's process identity, but specific methods need to use the original caller's identity.
  • Impersonating a specific Windows identity. You need to use a specific identity or several Windows identities to access particular resources.
  • Using delegation to access network resources by using an impersonated identity. You need to use an impersonated identity to access remote resources.
That's all folks!

Wednesday, August 5, 2009

Modifying <appSettings> in web.config programatically using AJAX, JQuery, JSON

Recently I was working on a ASP.Net project where there was a need to update the values stored in <appSettings> section in the web.config file programtically. Specifically, there is a maintenance screen with a tabbed interface and one of the tab was called 'Configuration' which would provide the user an interface to 'Add' and 'Update' <appSettings> values. One of the main requirements was that it has to be completely AJAX enabled and use of 'UpdatePanels' are not allowed.

You can find lot of resources on the web explaining how to do 'modify web.config programatically' and its an easy thing to do. But the main challenge lies in making it completely AJAX enabled (and there is not much information out there on this topic). And the steps to acheive that are as follows:
  1. Create a server method (e.g. GetConfigSettings()) to enumerate all the <appSettings> values
  2. Create a custom serializale type called 'AppplicationSetting' containing public members 'Key' and 'Value' and for each appsettings entry create a corresponding 'AppplicationSetting' objects and put it in a generic list (named 'AppSettingsList') of type'ApplicationSetting'
  3. Serialze the list data (from Step 2) in JSON format using JavaScriptSerializer
  4. Invoke the GetConfigSettings() from client side JavaScript on page load and retrieve the data (use PageMethods)
  5. Process the data (from Step 3) and create an user interface (as shown in Fig 1)
  6. Provide ability to add new values
  7. Process all the newly entered value and updated value on the client side ensuring duplicates are handled properly and create a JSON object
  8. On clicking 'Save' button, invoke a server method (e.g. SaveConfigSettings()) passing configuration settings (in form of a the JSON object)
  9. On the server side, deseriaize the data back to list of objects of type 'AppplicationSetting'
  10. Loop through the list (from Step 9) and add or update the corresponding key/value appSettings entry
  11. Save the web.config file
Now let's get cracking at the code (the fun stuff!!!). Let's start by looking at at the UI screen
(Fig 1)

The UI screen is self-explanatory. It consists of two textboxes for 'key' and 'value' for each of the existing appSettings value. Clicking on the 'Add' button adds a new pair of texboxes to enter a new key,value pair.

Clicking on the 'Cancel' reloads the form with the original (existing) values.
Define the public method GetConfigSettings() and mark it as a 'WebMethod()', make it 'Shared' so that is can be invoked from clientside JavaScript using PageMethods.

<WebMethod()>_
Public Shared Function GetConfigSettings() As String
Dim status As String = String.Empty
Dim bsrtent As ServiceLayer = New ServiceLayer
status = bsrtent.LoadConfigSettings()
Return status
End Function

<WebMethod()> _
Public Shared Function SaveConfigSettings(ByVal newconfigsettings As String) As String
Dim status As String = String.Empty
Dim bsrtent As ServiceLayer = New ServiceLayer
status = bsrtent.SaveConfigSettings(newconfigsettings)
Return status
End Function

Once we have marked a server method as 'WebMethod' we no longer can work with non-static members because the method is 'Shared'. So we need to define a class (essentially a service layer), instatiate an object of the service layer and invoke public instance member of the class to do the necessary functions.

The LoadConfigSettings function of the ServiceLayer is as follows:


The SaveConfigSettings() function of ServiceLayer is as follows:


Now to the client side JavaScript code (where all the action is!!!). We assume you have properly referred JQuery library in your project, use ScriptManager on your page and have a form with a div id='#tab-Config'. The JavaScript code in itself provides lots of insight into JQuery and its usage. (I'll blog about some of them in greater details in future)

$(function() {
PageMethods.GetConfigSettings(LoadConfigSettingsSuccess);
});

function LoadConfigSettings() {

if (CONFIGSETTINGS) {
var count = CONFIGSETTINGS.length;
var configrow;
if (count > 0) {
$('#tab-Config').empty();
var msg = '<b class="MSGRED"><u>Note</u>: Changing the web.config file will cause the application to re-start.</b>';

var configtable = $('<table><tr><td class="funcheader" align="center">Key</td><td class="funcheader" align="center">Value</td></tr></table>');
for (var i = 0; i < count; i++) {
var configrow = $('<tr></tr>');
var key = CONFIGSETTINGS[i].Key;
var val = CONFIGSETTINGS[i].Value;
var keytxt = $('<td><input type="text" name="configkey" id="configkey' + key + '" class="namemaptextbox" value="' + key + '"/></td>');
var valtxt = $('<td><input type="text" name="configval" id="configval' + key + '" class="namemaptextbox" value="' + val + '"/></td>');
configrow.append(keytxt);
configrow.append(valtxt);
configtable.append(configrow);
}
var buttontable = $('<table></table>');
var addkeybutton = $('<td><input type="button" class="clickbutton" name="addconfigbutton" id="addconfigbutton" value="Add"/></td>');
var saveconfigbutton = $('<td><input type="button" class="clickbutton" name="saveconfigbutton" id="saveconfigbutton" value="Save" onclick="SaveConfigSettings();"/></td>');
var cancelconfigbutton = $('<td><input type="button" class="clickbutton" name="cancelconfigbutton" id="cancelconfigbutton" value="Cancel" onclick="LoadConfigSettings();"/></td>');
buttontable.append($('<tr></tr>').append(addkeybutton).append(saveconfigbutton).append(cancelconfigbutton));

$('#tab-Config').append(msg).append(configtable).append(buttontable);

//Reset the width of the value textbox
$('div#tab-Config input[id^=configval]').each(function() { $('#' + this.id).css('width', '200px'); });

addkeybutton.click(function() {
var newkeytxt = $('<td><input type="text" name="configkey" class="namemaptextbox" value=""/></td>');
var newvaltxt = $('<td><input type="text" name="configval" class="namemaptextbox" value="" style="width:200px"/></td>');
configtable.append($('<tr></tr>').append(newkeytxt).append(newvaltxt));
});
}

}
}

function SaveConfigSettings() {
var keys = new Array();
var vals = new Array();
var newconfigsettings = "[";
$('#tab-Config input[name=configkey]').each(function() {
keys.push(this.value);
});
$('#tab-Config input[name=configval]').each(function() {
vals.push(this.value);
});

//Generate the JSON Objects
var prevkey = "";
for (var i = 0; i < keys.length; i++) {
//Check for duplicate
var dupkey = false;
for (var j = 0; j < i - 1; j++) {
if (keys[j] === keys[i]) {
dupkey = true;
break;
}
}
if (!dupkey) {
if ((keys[i] !== '') && (vals[i] !== '')) {
newconfigsettings += '{"Key":"' + keys[i] + '","Value":"' + vals[i].replace(/\\/g, '\\\\') + '"},';
prevkey = keys[i];
}
}
else {
i++; //found duplicate so skip the index for both both <key> and <val>
}

}
newconfigsettings += '{"Key":"EOF","Value":""}]';

PageMethods.SaveConfigSettings(newconfigsettings, SaveConfigSettingsSuccess, SaveConfigSettingsFailure);
}

function SaveConfigSettingsSuccess(result) {
var success = eval(result);
if ((success) && (success.Message)) {
alert(success.Message);
PageMethods.GetConfigSettings(LoadConfigSettingsSuccess);
}
}

function SaveConfigSettingsFailure(result) {
var err = eval(result);
alert(err.get_message());
}

function LoadConfigSettingsSuccess(result) {
eval(result);
LoadConfigSettings();
}

And that is all (really!!!) to create a pure AJAX enabled UI interface to programtically modify web.config appSettings entries. The same concept can be extended to update/modify other sections of the web.config (or any data) in an ASP.Net application.