4

I've been tasked to create a .NET Core (C# MVC) application that can be authenticated via Active Directory or Individual User Accounts.

There are a myriad of resources on the internet regarding setting up one or the other, and I've created applications with them. But is it possible to do both?

It would seem OAuth allows multiple authentication routes out of the box in .NET Core, but my guess is that Active Directory doesn't work that easily, being configured in IIS and using the operating system to authorize.

If it is not possible to do both - what are my options? I am guessing I would create two separate projects that do the same thing, but with different authentication - but maintaining two projects doesn't seem like a good idea.

Thanks in advance for any advice.

chakeda
  • 1,551
  • 1
  • 18
  • 40
  • Which should take precedence? If a username exists in both AD and as an individual account, which system is authoritative for that username? – Joel Coehoorn Sep 26 '18 at 04:25
  • @JoelCoehoorn We would deploy it with either AD or individual accounts depending on the client. – chakeda Sep 26 '18 at 04:29
  • 1
    You might want to think about the Provider pattern then. Define an interface for the account security provider, and then implement that interface twice, once for internal accounts and once for active directory. Maybe even a third time for unit test mocks. – Joel Coehoorn Sep 26 '18 at 15:19
  • That sounds like exactly what I want - but I am struggling to find a good resource for AD and individual, specifically. This seems to be a close one: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-custom-storage-providers?view=aspnetcore-2.1#reconfigure-app-to-use-a-new-storage-provider – chakeda Sep 26 '18 at 15:48

2 Answers2

3

I've cobbled together some resources, and I decided to create a simple, custom authentication that allows Active Directory and individual user accounts in my database.

First, I added ASP.NET Identity to my existing project. I made the Identity interfaces much more simpler than in the linked answer:

IdentityConfig.cs

public class IdentityConfig
{
    public void Configuration(IAppBuilder app)
    {
        app.CreatePerOwinContext(() => new Entities());
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Authentication/Login"),
        });
    }
}

Following @Sam's answer (yet again) on creating custom authentication/authorization in ASP.NET, I then created a simple Database-First user and role table in the database, not based off of Identity, and created a user manager class:

UserManager.cs

public class UserManager
{
    private Entities db = new Entities();

    public bool IsValid(string username, string password)
    {
        // TODO: salt and hash.
        return db.USER.Any(u => u.USERNAME == username && u.PASSWORD == password);
    }
}

Finally, to complete the custom authentication, I created a dead simple authentication controller. That will check if the user is valid, then create a ClaimIdentity.

AuthenticationController.cs

public class AuthenticationController : Controller
{
    private Entities db = new Entities();

    public ActionResult Login()
    {
        return View();
    }

    public ActionResult Logout()
    {
        HttpContext.GetOwinContext().Authentication.SignOut();
        return RedirectToAction("Index", "Home");
    }

    [HttpPost]
    public ActionResult Login(string username, string password)
    {
        UserManager um = new UserManager();
        bool valid = um.IsValid(username, password);

        if (valid)
        {
            // get user role and enditem
            USER user = db.USER.Where(u => u.USERNAME == username).First();
            string role = db.ROLE.Where(r => r.USERID == user.USERID).FirstOrDefault().ROLENAME;

            // create session
            Claim usernameClaim = new Claim(ClaimTypes.Name, username);
            Claim roleClaim = new Claim(ClaimTypes.Role, role);
            ClaimsIdentity identity = new ClaimsIdentity(
                new[] { usernameClaim, roleClaim }, DefaultAuthenticationTypes.ApplicationCookie
            );

            // auth succeed 
            HttpContext.GetOwinContext().Authentication.SignIn(new AuthenticationProperties { IsPersistent = false }, identity);
            return RedirectToAction("Index", "Home"); 
        }

        // invalid username or password
        ViewBag.error = "Invalid Username";
        return View();
    }
}

This simplicity seems exactly what I want, not carrying the massive bulk that is Identity or ASP.NET membership, and it works great.

Now to answer my original question - how do I also accommodate Active Directory users?

While I abstracted out and greatly simplified my USER and ROLE classes, there will be significant extra data that will be required for a USER (roles, permissions, etc) - and that data will not be in Active Directory - we need to create a USER regardless.

Therefore, all I need is to validate the username and password in Active Directory!

A quick configuration variable can change the control flow in the Login action, and execute this:

bool isValid = false;
if (authConfig == "AD") {
    using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "US"))
    {
        // validate the credentials
        isValid = pc.ValidateCredentials(username, password);
    }
} else if (authConfig == "Custom") {
    isValid = um.IsValid(username, password);
} 
// create claim...

This solution was possible because the only purpose of Active Directory authentication is to verify their user - no additional data was required from AD. Additionally, since we need to specify Role and other data in our custom tables, a custom user record had to be created anyway.

My confusion that spurred the answer was the lack of understanding of how flexible it is to create custom authentication in .NET, without using what they have baked in (like checking the "Individual User Accounts" option when creating a new project).

Other suggestions are welcomed, since this is the extent of my ASP.NET knowledge - but I believe this will work for my application. I hope this helps someone's authentication setup.

chakeda
  • 1,551
  • 1
  • 18
  • 40
1

you can work on authentication with the way you thought of or you can turn your focus to picking up IdentityServer4, a fully-featured authentication project for what would suit your needs.

Here's another stackoverflow question that is close to what you're looking for. If you're unfamiliar with how IS4 works, you can create a project based on their templates here;

dotnet new -i "identityserver4.templates::*"
Nicholas
  • 1,883
  • 21
  • 39
  • Thank you for the resource @Nicholas! Although I would prefer not having to use a 3rd party solution, I will consider it. – chakeda Sep 26 '18 at 04:31
  • @chakeda I'm a fan of not using 3rd party solutions! fret not because IS4 is a hyper supportive library. It's pretty much the first thing someone would think about when it comes to working on a new auth server on .NET Core. – Nicholas Sep 26 '18 at 08:10