Thursday 24 August 2006

'Mashing up' Windows AND Forms Authentication

Jeff

I had a classic requirement that a website must automatically log in users that have authenticated against its local domain controller (windows authentication). Any users who have not authenticated with its DC will need to login using a web based login form, which will then authenticate them against the DC using the ActiveDirectoryMembershipProvider.

I have used these resources to tackle this requirement:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/MixedSecurity.asp - Paul Wilsons msdn article titled "Mixing Forms and Windows Security in ASP.NET"

http://aspadvice.com/blogs/rjdudley/archive/2005/03/10/2562.aspx - Richard Dudley's blog about how he modified Pauls method to stop the browser popup for credentials for remote 'internet' users.

I'm just going to walk through my solution for my own reference and for anyone else with this requirement.

1. Make sure the whole website has the 'Enable Anonymous Access' checkbox ticked under IIS->Website->Properties->Directory Security->Edit->Enable Anonymous Access.
Note: The Integrated Windows authentication check box, under the Authenticated access, may also be selected as this is required to debug in VS.
2. Create both WinLogin.aspx and FormsLogin.aspx pages.
3. Create a Redirect401.htm file.
4. In the web.config file I have the following:

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0%22>
...
<location path="FormsLogin.aspx">
<system.web>
<authorization>
<allow users="?,*" />
</authorization>
</system.web>
</location>

<location path="WinLogin.aspx">
<system.web>
<authorization>
<allow users="?,*" />
</authorization>
</system.web>
</location>

<appSettings>
...
<add key="LanIPMask" value="192.168.\d{1,3}\.\d{1,3}"/>
...
</appSettings>


...
<system.web>
...
<authentication mode="Forms">
<forms name=".ADAuthCookie"
slidingExpiration="true" loginUrl="FormsLogin.aspx"/>
</authentication>
...
</system.web>
</configuration>

5. The FormsLogin just has the ASP.NET Login control and in the code behind of the I have the following:

protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);

// Is this a postback?
if (!Page.IsPostBack)
{
// NO - this is not a post back.

// Try to authenticate the user via windows Auth.
AttemptWindowsAuth();

// The user must authenticate using Forms.
}
}

private void AttemptWindowsAuth()
{
// Is the user not using internet explorer?
if (!Request.Browser.IsBrowser("IE"))
{
// NO - the user is not using IE and therefore can not perform windows authentication, don't redirect them.
return;
}

// Is the user on a mobile device?
if (Request.Browser.IsMobileDevice)
{
// YES - the user is on a mobile device, don't use windows auth.
return;
}

// Has the user already had a failed login?
if (Request.QueryString["failedlogin"] != null)
{
// YES - the user has already had a failed login, don't redirect them again.
return;
}
// Is the user on the local Lan?
if (Regex.IsMatch(this.Request.UserHostAddress, ConfigurationManager.AppSettings["LanIPMask"]))
{
// YES - the user is on the local lan so redirect them to the windows page for windows Auth.
RedirectToWinAuth();
}

// Is the user on the local server?
if (this.Request.UserHostAddress.Equals("127.0.0.1") this.Request.UserHostName.ToLower().Equals("localhost"))
{
// YES - the user is on the local server so redirect them to the windows page for windows Auth.
RedirectToWinAuth();
}
}

private void RedirectToWinAuth()
{
// Transfere to the windows login page.
Response.Redirect("WinLogin.aspx?" + Request.QueryString.ToString(), true);
}

5. In IIS make sure the WinLogin.aspx does NOT allow anonymous access and only uses Integrated Windows authentication to authentic access. This can be set by navigating to IIS->Website->WinLogin.aspx->Properties->Directory Security->Edit
6. Whilst you are in IIS navigate to IIS->Website->WinLogin.aspx->Properties->Custom Errors and change all the 401 errors to point to your Redirect401.htm file you created earlier.
7. The winLogin.aspx is an empty page and in the codebehind has the following:

protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
int start = this.Request.ServerVariables["LOGON_USER"].LastIndexOf('\\');
string userName = this.Request.ServerVariables["LOGON_USER"].Substring(start + 1);
FormsAuthentication.RedirectFromLoginPage(userName, false);
}

8. The Redirect401.htm has the following html:

<html xmlns="http://www.w3.org/1999/xhtml%22>
<head>
<title>Redirect 401</title>
<script type="text/javascript" language="javascript">
window.location = "FormsLogin.aspx?failedlogin=1"
</script>
</head>
<body>
<p>
If you are not automatically redirected please click <a href="FormsLogin.aspx?failedlogin=1">here</a>
</p>
</body>
</html>

So the users always hits the Formslogin page for authentication, it will check to see if their IP address matched the RegEx expression in the web config (which is the mask for local IP addresses) if it does then they are redirected to the Windows login page which will cause IIS to authenticate them using windows authentication. If this is successful they will be redirected via formsauthentication, giving them a forms authentication ticket :-) They are now free to move around the site.
If they are not in the local IP range they will be shown the forms login page for them to enter their details and use the ActiveDirectoryMembershipProvider to authenticate them against active directory.
If the user has just 'plugged into' the local domain and has received an IP address via the DHCP, when they visit the site they will be pushed to the windows authentication page and as they have not been authenticated by the DC they will be prompted with the browsers credential request box. If they cancel this or enter an invalid username and password combination a 401 error will be raised and handled by are custom page which will redirect them back to the FormsLogin page. The only way for them to gain access to the system is to enter a valid username and password that is stored in Active Directory.
I also use the SQLRolesProvider within this web application and it works fine with this solution.

Friday 18 August 2006

Winforms visibility and event firing

ScottI am currently working with a MDI application and at times MDI children forms toggle visibility for various reasons which i wont bore you with ;) With events being used quite extensively in the design i just wanted to share this little snippet ... when a winform has it's visible property set to false, the form events do not fire.

Winforms Combo Box and Sorted Property

Scott

Had a very strange one the other day ... the conclusion is the Sorted property of the forms.combobox control should never be true when the combo box is using a datasource.

The combo box was set up no different - Set the DataSource with a DataRow[], Set the DisplayMember and ValueMember. The DataRow[] was already in the desired Description ASC sort order;

IDDescription
302Alopecia
345Appetite Decreased
346Appetite Increased
303Behavioural Abnormality
304Bleeding
313Dystocia

So with the DisplayMember = Description column, the ValueMember = ID column and the Sorted property set to false.

As expected the SelectedValue returns the expected ID value.

However, with the DisplayMember = Description column, the ValueMember = ID column and the Sorted property set to true. The combo box data actually is;

IDDescription
302Alopecia
303Appetite Decreased
304Appetite Increased
305Behavioural Abnormality
306Bleeding
307Dystocia

So the ValueMember has begun from the first ID value and just incremented as each item was bound.

In this case the use of SelectedItem.ID returns the correct ID value.

So just my experience ... maybe obvious ... but i have not seen this documented anywhere and surely i cant be the first person to have experienced it ?!?!?!!?

Thursday 17 August 2006

Only validate when visible

Jeff

I hit a bit of a problem today with a ASP.NET Validator Control specifically the RequiredFieldValidator. I had a dropdown with the classic 'other' option which when selected shows a textbox for the user to specify the 'other'. I only wanted to have the RequiredFieldValidator fire when the textbox is visible!! This isn't built in with the control and it was just firing even when the textbox wasn't visible for the user to enter anything in it!! After a few hours of searching the web and going round in circles I finally came up with a solution :-)

I modified my javascript that shows/hides the textbox to include the action of disabling the validator too!! Here is my script:

// Will show/hide an object depending on a selection on a dropdown.
function ShowHideObject(dropDownID, dropDownShowString, objectToHideID, validatorToDisable)
{
// Get the selected text from the dropdown.
selectedText = document.getElementById(dropDownID).options[document.getElementById(dropDownID).selectedIndex].text;
// Set the object to hide to hidden.
document.getElementById(objectToHideID).style.visibility='hidden';
// Disable the validator.
document.getElementById(validatorToDisable).enabled=false;
// Does the selected text match the supplied dropDownShowString.
if (selectedText.match(dropDownString.toString()))
{
// YES - The selected text matches the supplied dropDownShowString.
// Show the object and enable the validator.
document.getElementById(objectToHideID).style.visibility='visible';
document.getElementById(validatorToDisable).enabled=true;
}
}

I then had to check before I called Page.isValid on the sever to see if the 'other' option was selected, if not then disable the validator.

All seems to be working cooool now :-)

Monday 14 August 2006

My RVC office

Jeff

Thought I would try out the 'Insert Map' function in the new Windows Live Writer. It uses Windows Live local and Virtual Earth, which now seems to include the UK :-) You can really zoom in quite far, and add pushpins. Any its quite a cool way to add maps to blogs, not sure how much I will use it though!! Anyway this is my office, Scott also lives here :-)


Windows Live Writer

JeffThis is my first blog using the new free Windows Live Writer (Beta) :-) You can download it here http://windowslivewriter.spaces.live.com/ Its pretty good, configured straight away with Blogger and pulls down all your styles. Has a nice and simple WYSIWYG interface, easy to insert photos and even maps - not that I've tried that yet!!

Wednesday 9 August 2006

Anyone for some Security Trimmings?

Jeff
I have used a SiteMap in my website to give a central repository of the site structure. I have also created a ul menu using a repeater control as shown here, as again the ASP.NET menu control uses tables to perform its layout.
What I really wanted was for the menu items to only be shown to users who are authorized to view that page. I could do this is the code behind by checking the Roles.IsUserInRole() method but wanted a more declarative method using my sitemap and role provider. This can be achieved by using security trimming. Enabling this feature on the SiteMap provider results in all the url's being checked again the url authorization rules. If the current user is not authorized to view the page it will not be included in the SitMap when used at runtime as a datasource. This results in my menu not rendering the link if the user is not authorized to view the page. Coooool :-)

ASP.NET 2.0 Roles, Forms Auth and Membership

Jeff
I'm not going to attempt to blog about how to setup ASP.NET 2.0 security, there are more than enough good blogs and How To's to get it working. The best place to start is from the awesome blogs of Scott Gu here.

What I do want to blog is my experiences of setting it up.
My scenario
I have a website that will be deployed on the WWW but will only be accessed by users contained in a AD. There will be two levels of users but not all users in the AD will have access.
My Solution
Ok first of all I would like to say that the Provider model used in ASP.NET 2.0 is spot on :-) It really does allow for less code, more productivity and a neat design.
First of all I am using Forms authentication with Active Directory as my membership provider. A great How To here. This provider along with the new LogIn control authenticates users against an LDAP store. I found the Login control really useful and nicely customizable, the only downside is the way the control renders in tables, which is a bit annoying for styling and you can't get full control over it. Other than that authenticating users via Forms auth is a lot easier than in .NET 1.1.
For my role management I originally wanted to use the AD roles but discovered that there is not yet an AD Role provider, and didn't really have the time to look into creating one!! So I opted for the SqlRoleProvider to manage my roles. There is a great Role Manager How To over here. This provider will use the aspnetdb database to store the roles and integrates well with the AD Membership provider by using the AD usernames. New Roles can be created and managed using the ASP.NET Web Site Administration Tool. I will have two roles, Admin and User and add only the users using the website to the roles from the AD store. After using the SQLRole provider I have realized that it may be a neater place to store role information than in the AD as it can all be stored in a central database, with no replication problems. Roles can be application specific by setting the application name in the providers tag in the web.config under the role manager tag. Here is my config:

<roleManager enabled="true" defaultProvider="SqlRoleManager" cacheRolesInCookie="true">
<providers>
<add name="SqlRoleManager"
type="System.Web.Security.SqlRoleProvider"
connectionStringName="SqlRoleManagerConnectionString"
applicationName="MyAppName" />
</providers>
</roleManager>

I then set my authorization tag in my web.config to allow only the users in my User and Admin roles and Deny everyone else. This ensures that only authenticated users in my roles can access the web site.

<authorization>
<allow roles="User" />
<allow roles="Admin" />
<deny users="*" />
<deny users="?" />
</authorization>

Deploying my role setup is the next issue I face. My plan is to run the Aspnet_regsql.exe tool to setup the aspnetdb database and then run SQL scripts to add the two roles. I have then created an admin page within my site which will add/remove users to these roles. Obviously the first time the web site is accessed no users will be in any roles and everyone will be locked out!! So I will amend the authorization tag in the web.config to allow the administrator user:

<authorization>
<allow users="Administrator" />
<allow roles="User" />
<allow roles="Admin" />
<deny users="*" />
<deny users="?" />
</authorization>

I have also set the following authorization tag up on the admin page as the following:

<location path="admin.aspx">
<system.web>
<authorization>
<allow users="Administrator" />
<allow roles="Admin" />
<deny users="*" />
</authorization>
</system.web>
</location>

This will allow the administrator user to access the site and the admin page to add all the users to the roles the first time the website is used.

Roll Over images

Jeff
I wanted to have my image buttons change on roll over on a website and really thought the ASP.NET ImageButton would of had a RollOverImageUrl property on it that automatically changed the image onmouseover and onmouseout. However I soon found out the is no such functionality :-(

So I extended the ImageButton and the ButtonField controls to have this property and handle the rollover. It simple to do just set the onmouseover and onmouseout attributes of the ImageButton to toggle the src to the correct url of the image. You can find the source here.

Mr Designer meets Mr Coder

Jeff
Been working with a web designer lately, something I have never done before. Its been very interesting seeing the two worlds meet. Mr Designer is an expert in HTML and CSS but knows little about the ASP.NET world.

The interesting areas have been the line between ASP.NET Skins and CSS and ASP.NET controls rendering in tables. I have decided that Skin vs CSS is not so much which one is best more which is most suitable when. I found Skins very powerful with controls when we can set properties and found CSS great for lay out and styling of HTML controls.

Mr Designer is obviously keen to give me his CSS to apply to my site, but sometimes I needed to adapt this to work with my site. I had to set the CssClass on many controls to use with the classes in the CSS and sometimes had to take parts out and just use the Skins to set the styles on ASP.NET controls.

Its quite frustrating that many of the ASP.NET controls render in tables as these are HTML heavy and not as flexible as using DIVs to style. Scott Gu has an interesting post here about the CSS Control Adapter Toolkit for ASP.NET 2.0, which looks like it could be interesting in solving this problem. When I get a chance I will have a play :-)

I still think their is a big gap between a pure web designer and a developer. It will be interesting to see if the Expression Products with WPF will help to shrink the gap :-)

Escape Characters

Jeff
Had a bit of an escape character jungle experience the other day. Had to use some HTML, JavaScript and C# escape characters all in the same day :-) So just thought I would record the links for future reference and for anyone else.

HTML escape characters

JavaScript escape characters

C# escape characters

Custom DateTime Format Strings

ScottSo simple, yet so easy to get mixed up :)
MSDN2 Custom DateTime Format Strings

Expandable gridview

Jeff

Been working on a project where I needed a grid which could have rows that expand with another grid within it for more details. I found this cool gridview over at The Code Project.

This control is great, it does exactly what it says and all credit to its authors. However I couldn't resist having a tinker with it :-) I have ended up amending it slightly.

The part I wanted to improve was where the client had to handle the RowCreated event, in order to bind any data to the child control in the item template and you had to set whether the row should expand or not. I just felt this could be encapsulated more by being handled internally by the control.

I set about solving this by adding the following extra properties to the control. These properties are designed around the nested object being another gridview, or expandable gridview, with some relationship between the two:

  • NestedGridName (string) - The name of a gridview which is nested.

  • NestedGridDataHandlerName (string) - The name of a datahandler object that is used to retrieve data for the nested grid, this maybe a TableAdapter or custom dataobject.

  • NestedGridDataHandlerMethodName (string) - The name of the method used to extract data on the data handler object.

  • NestedGridDataHandlerSingleton (bool) - Whether the data handler object is a singleton.

  • NestedGridDataHandlerInstanceProperty (string) - If the data handler is a singleton then this field must be set with the name of the property to access the instance.

  • NestedGridForeignDataKeyNames (string[]) - An array of foreign key column names on the containing grid used to reference the nested table.

  • ExpandCollapseCellPosition (int) - The position to add the cell which contains the Expand/Collapse button.


With these extra properties I was able to use reflection to call the data handler for the nested grid, get the data and bind it. I could also determine whether the row needed to be expanded. I also added the CellPosition so that the position of the expand/collapse button can be set and not always at the 0 position.

now you can use the control by dragging it onto the page, setting a few more properties, but can write zero code and it works :-) There are a few assumptions in here, such as the datahandler method takes the foreign key fields as arguments to retrieve the data for the nested grid and that the nested object is a grid. However if you don't set the DatahandlerName then the grid can be used as before with adding anything into the ItemTemplate field.

The other change I made was to make it XHTML compliant. The original version added a few attributes (expandClass, expandText, collapseClass, collapseText) to the grid, which were picked up in the javascript to perform the expand/collapse function. However these none standard attributes made it fail the XHTML standards. I changed this by adding the attributes to the javascript when it is built up in the OnInit method of the ExtGridView class. Now it has a big green XHTML pass :-)

You can download my modified version here

Tuesday 8 August 2006

System.Windows.Forms.Keys.Return & beep

ScottWhen using Keys.Return you will automatically be provided with the classic PC beep. If this is what you want then you are on a winner. However, if this is not what you want then this here is the gem to turn the beep off;
Within the KeyPress event, where e is KeyPressEventArgs.
e.Handled = true;

Friday 4 August 2006

DataSet Relationships

ScottBeen working quite heavily with strongly typed datasets at the moment. I keep getting asked the same question about the difference between relationship types. So in a simple nutshell;

Imagine two tables "Father" and "Son" with a relationship between the two.

Relationship type - Relation Only. Will generate intellisense method Son.FatherRow

Relationship type - Foreign Key Constraint Only. Will enforce relational integrity. No intellisense method as in Relation Only.

Relationship type - Both Relation and Foreign Key Constraint. Provides both intellisense method and relational integrity

Thursday 3 August 2006

Welcome

Welcome to our first post on our new blog site :-)

We are Jeff and Scott and just to be different we thought we would do a joint blog. We are both software developers currently working at the Royal Veterinary College in London. Between us we specialise in ASP.NET, C#, WinForms, enterprise level design and anything else we come across :-) We are often off on tangents trying things out so we thought we would record them here.

We are currently redeveloping our main site here. We are going to include this blog in our site along with all our favourite links, pictures and projects. We hope to get it finished soon :-)

Jeff Scott