Although OWIN have a package "Microsoft.Owin.Security.ActiveDirectory" seems can support AD login, but it looks like made for ADFS, and leak document on MSDN so I dunno how to use it on basic AD login, so I decide find other way to implement AD login to MVC.
After long long time search, this post "OWIN with LDAP Authentication" help me a lot, my solution basic on it and add some other feature.
App_Start\IdentityConfig.cs, add in ApplicationSignInManager
Controllers\AccountController.cs, rewrite login.
We add some information into user identity, now we need write some extension to easy get those info.
That's all, here has a little more info, if you meet Anti-forgery token error after those AD login modify , try add this line to global.cs
You can check this post "Anti-forgery token issue (MVC 5)" to get more detail.
After long long time search, this post "OWIN with LDAP Authentication" help me a lot, my solution basic on it and add some other feature.
App_Start\IdentityConfig.cs, add in ApplicationSignInManager
//user name without @domain
public async Task CheckActiveDirectoryPassword(string user, string password)
{
var domain = ConfigurationManager.AppSettings["LdapDomain"]; //ex: died.tw
var dn = ConfigurationManager.AppSettings["LdapDn"]; //ex: DC=died,DC=tw
var suffix = ConfigurationManager.AppSettings["LdapPrincipal"]; //ex: @died.tw
PrincipalContext dc = new PrincipalContext(ContextType.Domain, domain, dn, user+suffix, password);
return await Task.Run(() =>
{
bool authenticated = dc.ValidateCredentials(user, password);
if (!authenticated) return new InnerResult {Success = false};
//get user info in AD if auth success
var info = UserPrincipal.FindByIdentity(dc, IdentityType.SamAccountName, $"{domain}\\{user}");
return new InnerResult {Success = true, Data = info};
});
}
Controllers\AccountController.cs, rewrite login.
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var result = await SignInManager.CheckActiveDirectoryPassword(model.Name, model.Password);
if (result.Success)
{
// add user info
UserPrincipal info = result.Data;
var claims = new List
{
new Claim(ClaimTypes.Sid, info.Sid.ToString()),
new Claim(ClaimTypes.X500DistinguishedName, info.DistinguishedName),
new Claim(ClaimTypes.GivenName, info.GivenName),
new Claim(ClaimTypes.Surname, info.Surname)
};
// if need trust relationshop, check this https://support.microsoft.com/en-us/help/2771040/the-trust-relationship-between-this-workstation-and-the-primary-domain
/*
foreach (var group in Request.LogonUserIdentity.Groups)
{
string role = new SecurityIdentifier(group.Value).Translate(typeof(NTAccount)).Value;
string clean = role.Substring(role.IndexOf("\\", StringComparison.Ordinal) + 1, role.Length - (role.IndexOf("\\", StringComparison.Ordinal) + 1));
claims.Add(new Claim(ClaimTypes.Role, clean));
}*/
// if want to implement with membership, add info in ApplicationUser then login, and add custom user claims in IdentityModels.cs
/*
ApplicationUser user = new ApplicationUser {UserName = model.Name,Email = info.UserPrincipalName };
SignInManager.SignIn(user, false, true);*/
// didn't use membership
ClaimsIdentity ci = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties
{
AllowRefresh = true,
IsPersistent = false,
ExpiresUtc = DateTime.UtcNow.AddDays(7)
}, ci);
return RedirectToLocal(returnUrl);
}
ModelState.AddModelError("", "Invalid login credentials.");
return View(model);
}
We add some information into user identity, now we need write some extension to easy get those info.
public static class UserExtension
{
public static string GetSid(this IPrincipal user)
{
var claim = ((ClaimsIdentity)user.Identity).FindFirst(ClaimTypes.Sid);
return claim?.Value;
}
public static string GetX500DistinguishedName(this IPrincipal user)
{
var claim = ((ClaimsIdentity)user.Identity).FindFirst(ClaimTypes.X500DistinguishedName);
return claim?.Value;
}
public static string GetGivenName(this IPrincipal user)
{
var claim = ((ClaimsIdentity)user.Identity).FindFirst(ClaimTypes.GivenName);
return claim?.Value;
}
public static string GetSurname(this IPrincipal user)
{
var claim = ((ClaimsIdentity)user.Identity).FindFirst(ClaimTypes.Surname);
return claim?.Value;
}
}
//custom class for return value
public class InnerResult
{
public bool Success { get; set; }
public string Message { get; set; }
public int Code { get; set; }
public dynamic Data { get; set; }
}
That's all, here has a little more info, if you meet Anti-forgery token error after those AD login modify , try add this line to global.cs
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
You can check this post "Anti-forgery token issue (MVC 5)" to get more detail.
No comments:
Post a Comment