39

It's easy to create an ASP.NET MVC application that authenticates based on windows domain user. It's also easy to create one that uses individual accounts stored using Entity Framework. In fact, there are project templates for both.

But I want to utilize BOTH kinds of authentication in the same application. I tried to combine the code from both project templates. I have a problem in Startup.Auth.cs.

// from "Individual Accounts" template
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString("/Account/Login"),
    Provider = new CookieAuthenticationProvider
    {
        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
            validateInterval: TimeSpan.FromMinutes(30),
            regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
    }
});

The existence of cookie authentication owin middleware seems to cause domain identities to become un-authenticated. If I take this line out, the domain authentication works. But without it, I can't seem to support individual user accounts.

I've downloaded the katana project source code and examined CookieAuthenticationHandler.cs, but I don't quite understand how it works in the context of an OWIN pipeline.

How can I use the ASP.net identity framework to allow my application to authenticate users from the windows domain OR an application-specific user store?

Dave Alperovich
  • 32,320
  • 8
  • 79
  • 101
recursive
  • 83,943
  • 34
  • 151
  • 241
  • This answer can help http://stackoverflow.com/a/30160231/809357 – trailmax May 12 '15 at 22:32
  • Thanks @trailmax. It is certainly a related question, but I'd like to avoid creating application users for active directory authenticated users if I can. – recursive May 12 '15 at 23:17
  • 3
    The problem with trying to avoid creating app users for domain accounts is that your application specific tables referencing those domain accounts are going to look really funky. If a user has, say, a role that only exists in your app (and not the domain) you have to blast away your FK constraints to account for it. Application specific users are the best way of doing this. Your app would just authenticate domain users against AD and not against your app. – jamesSampica May 13 '15 at 05:44
  • I was hoping domain users could have domain roles, and app users could have app roles. I think i can avoid the need to cross that boundary with a relationship. – recursive May 13 '15 at 06:05
  • Can you finish your question there at the end? I *think* you are asking how to log in the user with their Windows account if they come to the app with that auth information. If they don't have that, you want to fall back to Cookie auth (i.e. Forms auth). This fallback would accept the cookie if valid and log that user in. And the final choice, if they come to the app with no cookie or an expired cookie, bounce them to the cookie auth LoginPath. Is that a correct assumption? – Chris Simmons May 14 '15 at 21:14
  • To use the windows authentication and form authentication in the same application, you need to configure IIS to run classic mode in which IIS windows authentication and asp.net formauthentication will run in two pipeline. So it is possible to mix the two authentication. – Dave Alperovich May 14 '15 at 21:36
  • 1
    The simplest approach is to have 2 different presentation Projects only for Authentication/Authorization. This has the advantage of leaning on existing framework and standard configuration. From there, you decide to either **A)** create an AD user for every internet user, or **B)** create a DB/Internet user for every AD user. If you take this approach. **B** is easier to implement further. Once passed the Authentication/Authorization Projects, have all users operate in subdomains that look for common authentication cookie Or universal Claims. – Dave Alperovich May 14 '15 at 23:04
  • 2
    If you want a truly Unified Solution, this is the approach I would start with `Install-Package OWIN-MixedAuth` https://github.com/MohammadYounes/Owin-MixedAuth – Dave Alperovich May 14 '15 at 23:10
  • I do this, but I had to step back (and simplify) and use the old Asp.Net `FormsAuthentication` stuff with my MVC app. Surprisingly much cleaner. – mxmissile May 21 '15 at 13:07

3 Answers3

15

The simplest approach is to have 2 different presentation Projects only for Authentication/Authorization.

This has the advantage of leaning on existing framework and standard configuration.

From there, you decide to either

  • create an AD user for every internet user, or
  • create a DB/Internet user for every AD user.

Creating an Identity user for each AD user is easier to implement further. Then the same cookies and filters can exist in the entire app.

In that case you can either

  • use subdomain(s) for your app
  • AD Authentiction Project can have the singular purpose of Authentication / Authorization, then the Web App can represent the rest of your app.

Alternatively, If you want a truly Unified Solution, use MohammadYounes/Owin-MixedAuth

MohammadYounes/Owin-MixedAuth

Install-Package OWIN-MixedAuth

In Web.config

<location path="MixedAuth">
  <system.webServer>
    <security>
      <authentication>
        <windowsAuthentication enabled="true" />
      </authentication>
    </security>
  </system.webServer>
</location>

In in Startup.Auth.cs

app.UseMixedAuth(cookieOptions);

:

:

How it works:

The handler uses ApplyResponseChallengeAsync to confirm the request is a 401 challenge. If so, it redirects to the callback path to request authentication from IIS which is configured to query the AD.

        AuthenticationResponseChallenge challenge = Helper.LookupChallenge(
              Options.AuthenticationType, Options.AuthenticationMode);

A 401 challenge is caused by an unauthorized users attempting to use a resource that requires Authentication

The handler uses InvokeAsync to check if a request is coming from a callback path (IIS) and then calls AuthenticateCoreAsync

    protected async override System.Threading.Tasks.Task<AuthenticationTicket>
                AuthenticateCoreAsync()
    {
        AuthenticationProperties properties = UnpackStateParameter(Request.Query);

        if (properties != null)
        {
            var logonUserIdentity = Options.Provider.GetLogonUserIdentity(Context);

            if (logonUserIdentity.AuthenticationType != Options.CookieOptions.AuthenticationType
                && logonUserIdentity.IsAuthenticated)
            {
                AddCookieBackIfExists();

                ClaimsIdentity claimsIdentity = new ClaimsIdentity(
                    logonUserIdentity.Claims, Options.SignInAsAuthenticationType);

                //  ExternalLoginInfo GetExternalLoginInfo(AuthenticateResult result)
                claimsIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier,
                  logonUserIdentity.User.Value, null, Options.AuthenticationType));

                //could grab email from AD and add it to the claims list.
                var ticket = new AuthenticationTicket(claimsIdentity, properties);

                var context = new MixedAuthAuthenticatedContext(
                   Context,
                   claimsIdentity,
                   properties,
                   Options.AccessTokenFormat.Protect(ticket));

                await Options.Provider.Authenticated(context);

                return ticket;
            }
        }
        return new AuthenticationTicket(null, properties);
    }

AuthenticateCoreAsync uses AddCookieBackIfExists to read the claims cookie created by AD and creates it's own Claims based.

AD users are provided a Claims based Cookie identical to Web Users. AD is now like any other 3rd party authenticator (Google, FB, LinkedIN)

Dave Alperovich
  • 32,320
  • 8
  • 79
  • 101
7

It's for this reason that I haven't been able to use pre-baked solutions for authentication. In our project, the passing years (and agile approach) have left us with 4 different ways to authenticate which is annoying, but we support all legacy versions of apps in the field so we have to preserve it all (at least for now).

I ended up creating a factory that figures out the authentication mechanism (through any of several means such as token format, presence of some other thing) and then returns a wrapper that carries the logic for validating that authentication method and setting the principal.

This gets kicked off in a custom HTTP module so that the principal is built and authenticated before the request gets to the controller. In your case, windows Auth would be the final fallback, I think. In our Web API application, we took the same approach but through a delegating handler instead of HTTP module. It's a type of local token federation, you could say. The current implementation allows us to add or modify any validation procedure, or add any other token format; in the end, the user ends up with a proper identity or gets denied. Only took a few days to implement.

Dave Alperovich
  • 32,320
  • 8
  • 79
  • 101
Sinaesthetic
  • 11,426
  • 28
  • 107
  • 176
2

It seems to me the best answer to this question is to use an authentication and authorization framework. There are plenty to choose from (both commercial and free). You could, of course, write your own but I would discourage it. Lots of very smart people get this wrong.

I would take a look at IdentityServer3. It's certainly not the only solution but its a pretty good authentication and authorization framework. It's open source and pretty easy to get up and running in a hurry. Your use case is a common one and you will find some very useful information at the link above. Clean separation between authorization and authentication, social authentication options, easy to work with json web tokens that encapsulate user claims, etc.

How it can help you

IdentityServer3 allows you to configure Identity Providers to handle authentication and there are plenty of extension points that will allow you to implement a chain of responsibility that can handle both of your scenarios. From the docs:

IdentityServer supports authentication using external identity providers. The external authentication mechanism must be encapsulated in a Katana authentication middleware.

Katana itself ships with middleware for Google, Facebook, Twitter, Microsoft Accounts, WS-Federation and OpenID Connect - but there are also community developed middlewares (including Yahoo, LinkedIn, and SAML2p).

To configure the middleware for the external providers, add a method to your project that accepts an IAppBuilder and a string as parameters.

IdentityServer3 supports AD as an identity providor via a browser login window and will support a more programmatic implementation via a custom grant. You can also take a look here for some more information on IdentityServer3 and AD authentication.

It will support windows authentication as well and you can take a look at here for information and examples on implementing that.

There is a pretty good getting started example here as well.

With the right configuration IdentityServer3 can handle your requirements. You will need to implement your own authentication providers and plug them into the framework but there isn't much more to it than that. As for authorization goes, there are plenty of options there as well.

Community
  • 1
  • 1
Brandt
  • 121
  • 7
  • Brandt, so far you have offered a link and suggested using IdentityServer3 or learning from it. But are you really adding any value? Is this an answer or a comment? – Dave Alperovich May 21 '15 at 14:56
  • If you want this to be an answer, expand on why you like IdentityServer3 in particular. Please expand on where you think OP should look and what is worth learning. Please expand on benefits that make it ideal for OP's needs and drawbacks that may make it less than ideal. – Dave Alperovich May 21 '15 at 14:56
  • Added more specific information in response to the comments above. The OP asks a nuanced question with a lot of possible answers. Hopefully the edit provides enough information to be helpful. – Brandt May 21 '15 at 17:02
  • 1
    That is much better. And more information. But I don't see the OP being at all nuanced. Request is specific **Using Windows Domain accounts AND application-managed accounts**. And more specifically, how to set cookie authentication for both since the frameworks support different solutions. – Dave Alperovich May 21 '15 at 17:14
  • Brandt, OP has already downloaded katana project source code and examined CookieAuthenticationHandler.cs. As far as I have read through your link, so far, I don't see any mention of AD or Windows Authentication. Do you know for certain that IdentityServer3 handles Windows Authentication? – Dave Alperovich May 21 '15 at 17:15
  • Yes, AD is supported out of the box as an identity provider via a browser login screen. A programmatic approach is also fairly strait forward and is implemented via a custom grant. See https://github.com/IdentityServer/IdentityServer3/issues/618 – Brandt May 21 '15 at 18:42
  • Windows authentication is also baked in. Don't have a link handy atm but I'll post one in a few (walking into a meeting) – Brandt May 21 '15 at 18:48
  • Please find it and add it to your answer. A good answer is not "go look". It's here's a tool. Here is how it works. Here are features that answer your needs. Here is how it can help... **BTW** your answer is improving and you're being rewarded for it. – Dave Alperovich May 21 '15 at 18:54
  • There is some useful information [here](https://github.com/IdentityServer/WindowsAuthentication) regarding windows authentication integration. I am commenting now (as I am on my mobile device) but I'll update my answer later and include the links in the comments. – Brandt May 21 '15 at 19:21
  • I understand how it's hard to work on this while you have real work to do. When you get the chance, add information about how Thinctecture's IdentityServer3 supports Windows Authentication AND Application Identity simultaneously and this will become a good answer. LMK and I will vote it up. Good Luck. – Dave Alperovich May 21 '15 at 19:24