0
votes

I have an ASP.Net MVC 5 application which is currently using individual authentication (account/login.cshtml page with no authentication/anonymous access) and OWIN. Works fine.

As this is an intranet app I want to allow the users to log in under their windows account, another users windows account or an application account(admin, special user etc. - these accounts have no associated domain account).

For the first option I wanted to display their windows username on the login screen and they can simply click the "ok" button to login. To get the username I modified the Visual Studio Project properties to disable anonymous authentication and enable windows authentication. Also modified the web.config and set the authentication mode to Forms. This causes "HTTP Error 404.15 - Not Found". This appears to be due to an authentication loop caused by OWIN with the following suggestions to fix:

  • Ensure Login controller methods allow anonymous access (seems to be this way by default).
  • or Modify Startup.auth, comment out the LoginPath property.
  • or Modify the web.config, add the appSetting "owin:AutomaticAppStartup" with value "false".

I opted for the LoginPath fix and this appears to work (as does web.config change) in that there are no errors and the login page displays with the windows username (retrieved using System.Threading.Thread.Currentprinciple.Identity.Name).

The problem is now that once the user has logged in the OwinContext has no user ( HttpContext.GetOwinContext().GetUserManager()).

Ideally I don't need IIS or OWIN doing any authentication as it's done by the app - but I need the initial request (for the account/login page) to include the Authenticate headers so I can get the windows user.

Firstly I would like to understand what causes the "HTTP Error 404.15" and fix. Secondly, how do I get OWIN to work with the authentication change - I just need it to persist the user for controller authentication.

1

1 Answers

0
votes

This is just a guess but I believe the error is caused by the misconfiguration you've described: you have set the authentication mode to "Forms" but set the project to use Windows Authentication. It can be confusing but Windows Authentication is not Forms Authentication. When you are using Forms Authentication the user provides the credentials in the form that is submitted, validated (including all anti-forgery goodness) against the user store (I believe you are using ASP.NET Identity which would be a default for "Individual Authentication" setting) and if the validation is successful a cookie to set is included in the response. This cookie is then used to authenticate further requests.

As confirmed by Katana documentation, there is no built-in middleware for Windows Authentication - Microsoft simply assumes that IIS should be used for that. Which effectively prevents us from easily combining Katana OWIN middleware providers with Windows authentication. Now, easily is the key word: we still can "hack" our way around it.

Unfortunately, it still will be a hack: I have not found a way to make the authentication "transparent" (as in "a user opens the login form and can enter both the AD account credentials or the individual account credentials and everything just works"). You will need to maintain the individual account record for every Windows user (as you would do with any external OWIN middleware, such as Google or Facebook). You can automate the account creation and association though and make it look transparent. You can add an "external provider" button for your Windows authentication.

Authenticating the user would look like (in a separate "AD Authentication" controller):

bool userWindowsAuthentication = Request.LogonUserIdentity.IsAuthenticated;

if (userWindowsAuthentication) {
    var userStoreDatabaseContext = new ApplicationDbContext();
    var userStore = new UserStore<UserModel>(userStoreDatabaseContext);
    var userStoreManager = new UserManager<UserModel>(userStore);
    var userWindowsLoginCredentials = GetWindowsLoginInfo();

    var existingInternalUser = userStoreManager.FindAsync(userWindowsLoginCredentials.UserName)

    if (existingInternalUser) {
       // It means that the user already exists in the internal provider and here you simply authenticate and redirect to destination
    } else {
       // It means that the user does not exist. You can automatically create the internal user record here and associate the Windows user with the internal record.
    }
} else {
    // It means that user is not signed in using Windows authentication, so you either want to redirect back to the login page or restrict access or do something else
}

As you can see, it's "dirty". Another hack: you can have additional layer (separate application or a virtual application) that accepts only Windows authentication. This app can be your log-in resource. If the user is authenticated with Windows AD you can redirect them to the correct login page. You can go even further and add their login info in the redirect request header but if you do so - the header must be encrypted to ensure that Windows authentication cannot be faked and the only thing that should be able to decrypt and validate it should be your main application. Again, dirty, but works.