The situation: You have a Website that is available to both Intranet users and Internet users. You want users from the Intranet to be authenticated automatically using their domain credentials and Internet users to be authenticated using basic ASP.NET forms security.
The problem: When you configure IIS to allow both Anonymous Access and Integrated Windows Authentication, you cannot get the remote users NTLM name.
The fix: Configure IIS to use Anonymous Access for the entire site, with the exception of a single page. This page will be set to use Integrated Windows Authentication.
The gotcha is that, when you check "Anonymous Access" in IIS, you cannot get the user's NTLM name (even if you check "Integrated Windows Authentication"). The key is to specify "Integrated Windows Authentication" for exactly one page in your application. When Intranet users hit this page, we can get their NTLM name out of Request.ServerVariables["LOGON_USER"] and programmatically store that in the FormsAuthentication context. External Internet users can even hit this page and manually specify their domain credentials if they like, but these users will normally go through the standard login process.
STEP 1 - Create a new virtual directory in IIS. In the application root, only check "Anonymous Access". Do not check "Integrated Windows Authentication". This should look like:
STEP 2 - In your Web application, create a new form named "WinLogin.aspx" under the app's root. Also add a page named "Login.aspx" under the app's root and put an instance of the standard System.Web.UI.WebControls.Login control on it, e.g.:
STEP 3 - In IIS manager, select WinLogin.aspx and navigate to the "File Security" tab. Here disable "Anonymous Access" but enable "Integrated Windows Authentication" (this is crucial). This should look like:
STEP 4 - In your web application's web.config, add the following:
"Login.aspx">
"?,*" />
"WinLogin.aspx">
"?,*" />
"Forms">
"Login.aspx"/>
"?" />
"*" />
"CustomMembershipProvider">
"CustomMembershipProvider" type="DualAuthenticationExample.CustomMembershipProvider, DualAuthenticationExample" />
STEP 5 - In ~/Login.aspx add the following code:
namespace DualAuthenticationExample
{
public partial class LoginPage : System.Web.UI.Page
{
private bool IsIntranetRequest(string ip)
{
// TODO: Check for whatever interanet ip addresses, ranges, etc here...
return !string.IsNullOrEmpty(ip) && Regex.IsMatch(ip, "^127");
}
protected void Page_Load(object sender, EventArgs e)
{
if (IsIntranetRequest(Request.ServerVariables["REMOTE_ADDR"]))
{
Response.Redirect("~/WinLogin.aspx");
}
}
}
}
STEP 6 - In ~/WinLogin.aspx add the following code:
namespace DualAuthenticationExample
{
public partial class WinLoginPage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string user = Request.ServerVariables["LOGON_USER"];
if (string.IsNullOrEmpty(user))
{
Response.Redirect("~/Login.aspx");
}
else
{
FormsAuthentication.SetAuthCookie(user, false);
Response.Redirect("~/Default.aspx");
}
}
}
}
If you know the IP range(s) for the Intranet requests you can really streamline what I have here and make it seamless to the user. This is technically not a requirement to get this to work but, without it, external users will be prompted with the authentication box (I believe the behavior for this varies between browsers and also depends on the user's current browser settings).
No comments:
Post a Comment