0

I have an ASP.Net Application. The requirement is to implement Form Authentication using ADFS. If the user is accessing the WebSite from within the Domain(the same Domain as the Active Directoris), then the Form Authentication should be performed. i.e. Using the User's Windows logged in email Id, we should check if the user exists in the Active Directory or not. If the user exists, then the Website is made accessible to the user. If the user is not found on the basis of his/her email id, then the user is asked his/her UserName and Password, and to select one of the Two Active Directories on which the user should be searched. (PN: There are two Active Directories. One Default for using with-in the Domain.)

If the User is accessing the Website from outside the Domain, then the user is always asked his/her UserName and Password, and to select one of the two Active Directories to which the User Belongs.

So, there is one URL to access the Website from with in the Domain, and one to access from Outside the Domain.

And I need help to accomplish this task. The project is in Dot.Net, using Framework 3.5 on ASP.Net and C#.

Help with code solution highly appreciated.

Ajinx
  • 11
  • 2

2 Answers2

1

I have done this. The basic idea is that your main form of authentication is Forms. However you make your default login page use Windows authentication. If the Windows authentication succeeds, then you create the Forms ticket and proceed. If not, then you display the login page.

The only caveat is that since Windows authentication always sends a 401 response to the browser (challenging it for Windows credentials), then non-Domain users will always get a credentials pop-up that they will have to click Cancel on.

I used MVC in my project. My Windows login page is /Login/Windows and my manual login page is /Login.

Here are the relevant areas of my web.config:

<system.web>
  <authentication mode="Forms">
    <forms loginUrl="~/Login/Windows" defaultUrl="~/" name=".MVCFORMSAUTH" protection="All" timeout="2880" slidingExpiration="true" />
  </authentication>
<system.web>

<location path="Login">
  <system.web>
    <authorization>
      <allow users="?" />
      <allow users="*" />
    </authorization>
  </system.web>
</location>
<location path="Login/Windows">
  <system.webServer>
    <security>
      <authentication>
        <windowsAuthentication enabled="true" />
        <anonymousAuthentication enabled="false" />
      </authentication>
    </security>
    <httpErrors errorMode="Detailed" />
  </system.webServer>
  <system.web>
    <authorization>
      <allow users="?" />
    </authorization>
  </system.web>
</location>

Here is my LoginController:

[RoutePrefix("Login")]
public class LoginController : Controller {

    [Route("")]
    public ActionResult Login() {
        //Clear previous credentials
        if (Request.IsAuthenticated) {
            FormsAuthentication.SignOut();
            Session.RemoveAll();
            Session.Clear();
            Session.Abandon();
        }
        return View();
    }

    [Route("")]
    [HttpPost]
    public ActionResult TryLogin(string username, string password) {
        //Verify username and password however you need to

        FormsAuthentication.RedirectFromLoginPage(username, true);
        return null;
    }

    [Route("Windows")]
    public ActionResult Windows() {
        var principal = Thread.CurrentPrincipal;
        if (principal == null || !principal.Identity.IsAuthenticated) {
            //Windows authentication failed
            return Redirect(Url.Action("Login", "Login") + "?" + Request.QueryString);
        }

        //User is validated, so let's set the authentication cookie
        FormsAuthentication.RedirectFromLoginPage(principal.Identity.Name, true);
        return null;
    }
}

Your Login View will just be a normal username / password form that does a POST to /Login.

At this point, you have a /Login page that people can manually go to to login. You also have a /Login/Windows page that is the default login page that people are automatically redirected to. But if Windows login fails, it'll display a generic 401 error page.

The key to making this seamless is using your Login view as your custom 401 error page. I did that by highjacking the response content in Application_EndRequest using the ViewRenderer class written by Rick Strahl.

Global.asax.cs:

protected void Application_EndRequest(object sender, EventArgs e) {
    if (Response.StatusCode != 401 || !Request.Url.ToString().Contains("Login/Windows")) return;

    //If Windows authentication failed, inject the forms login page as the response content
    Response.ClearContent();
    var r = new ViewRenderer();
    Response.Write(r.RenderViewToString("~/Views/Login/Login.cshtml"));
}

Another caveat I've found is that this doesn't work in IIS Express (although it's been a version or two since I last tried). I have it setup in IIS and point the debugger at that.

Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84
  • Thanks. You are absolutely correct in understanding my requirement. But since I am not that well versed with MVC, can you please give me the code in ASP.Net C#. I mean where exactly to specify the AD path? How to get the details of the user, using the Windows Logged in user's email? How do I then use these details and allow the user to access the Website. I believe there is something called tokens. – Ajinx Apr 06 '17 at 11:00
  • MVC is part of ASP.NET, and the code I've shown is already in C# :) When Windows authentication succeeds, the user's information is available in `Thread.CurrentPrincipal`, which is used in my code example. The Forms authentication token, which allows access, is created using `FormsAuthentication.RedirectFromLoginPage`. – Gabriel Luci Apr 06 '17 at 14:15
0

There is an OOTB solution that may work for you.

Use a ADFS WAP as well and set up split-brain DNS.

Internal users (inside the domain) get the DNS of the ADFS box. The default is Windows auth. (IWA)

External users (outside the domain) get the DNS of the ADFS WAP box. The default is FBA.

rbrayb
  • 46,440
  • 34
  • 114
  • 174