I'm doing something very similar at the moment too. I'm providing a single sign-on portal for internal and external users where they can log in with their AD account or a specified user/password combination.
How I'm currently achieving this (note this is still a work in progress) is in the following way. I'm also using the ASP.NET Identity 2.1 alpha that includes the SignInManager (very cool).
- Set up user accounts with an optional password (must be specified for non-AD users)
- Associate a UserLogin to the account for AD users with ProviderKey equal to their AD account Sid
- On the login I detect if
Request.LogonUserIdentity
has an known account. Then check if they are a valid use by using the UserManager.FindAsync
method. From here you can challenge them again, provide them with an option to log directly in as the known user or log them straight in (your choice here).
- I then also allow them to log in via the standard user login form by detecting a username entered in the domain\username format. This allows for a domain user to log in when they are entering your site externally or from another users computer.
Some code snippets from this process (these are just some samples to get you going as the complete solution is spread over my solution).
Log in a using the Request.LoginUserIdentity. This could be a method in your account controller.
public async Task<ActionResult> WindowsLogin(string returnUrl)
{
var loginInfo = GetWindowsLoginInfo();
var user = await _userManager.FindAsync(loginInfo);
if (user != null)
{
await SignInAsync(user, false);
return RedirectTo(returnUrl, "Manage");
}
return RedirectToAction("Login");
}
private UserLoginInfo GetWindowsLoginInfo()
{
if (Request.LogonUserIdentity == null || Request.LogonUserIdentity.User == null)
{
return null;
}
return new UserLoginInfo("Windows", Request.LogonUserIdentity.User.ToString());
}
I have also added a method to my ApplicationSignInManager (inherits from SignInManager) to allow a user to log in with their AD details using the standard login form.
public async Task<SignInStatus> WindowsLoginAsync(string userName, string password, bool isPersistent)
{
var signInStatus = SignInStatus.Failure;
using (var context = new PrincipalContext(ContextType.Domain, "YourDomain"))
{
// validate the credentials
bool credentialsValid = context.ValidateCredentials(userName, password);
if (credentialsValid)
{
UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(context, userName);
if (userPrincipal != null)
{
var loginInfo = new ExternalLoginInfo
{
Login = new UserLoginInfo(AuthenticationTypes.Windows, userPrincipal.Sid.ToString())
};
signInStatus = await ExternalSignInAsync(loginInfo, isPersistent);
}
}
}
return signInStatus;
}
Then this can be used in your Login method like this.
Regex domainRegex = new Regex("(domain\\.+)|(.+@domain)");
if (domainRegex.IsMatch(model.Username))
{
result = await _signInManager.WindowsLoginAsync(model.Username, model.Password, model.RememberMe);
switch (result)
{
case SignInStatus.Success:
return RedirectTo(returnUrl, "Manage");
}
}
result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, true);
...
I hope some of this helps you solve the problem!