Showing posts with label Web Security. Show all posts
Showing posts with label Web Security. Show all posts

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.

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.