It's an outdated article, but http://msdn.microsoft.com/en-us/library/ff650308.aspx#paght000026_step3 illustrates what I want to do. I've chosen Nancy as my web framework because of it's simplicity and low-ceremony approach. So, I need a way to authenticate against Active Directory using Nancy.
In ASP.NET, it looks like you can just switch between a db-based membership provider and Active Directory just by some settings in your web.config file. I don't need that specifically, but the ability to switch between dev and production would be amazing.
How can this be done?
Really the solution is much simpler than it may seem. Just think of Active Directory as a repository for your users (just like a database). All you need to do is query AD to verify that the username and password entered are valid. SO, just use Nancy's Forms Validation and handle the connetion to AD in your implementation of IUserMapper. Here's what I came up with for my user mapper:
public class ActiveDirectoryUserMapper : IUserMapper, IUserLoginManager
{
static readonly Dictionary<Guid, long> LoggedInUserIds = new Dictionary<Guid, long>();
readonly IAdminUserValidator _adminUserValidator;
readonly IAdminUserFetcher _adminUserFetcher;
readonly ISessionContainer _sessionContainer;
public ActiveDirectoryUserMapper(IAdminUserValidator adminUserValidator, IAdminUserFetcher adminUserFetcher, ISessionContainer sessionContainer)
{
_adminUserValidator = adminUserValidator;
_adminUserFetcher = adminUserFetcher;
_sessionContainer = sessionContainer;
}
public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)
{
_sessionContainer.OpenSession();
var adminUserId = LoggedInUserIds.First(x => x.Key == identifier).Value;
var adminUser = _adminUserFetcher.GetAdminUser(adminUserId);
return new ApiUserIdentity(adminUser);
}
public Guid Login(string username, string clearTextPassword, string domain)
{
var adminUser = _adminUserValidator.ValidateAndReturnAdminUser(username, clearTextPassword, domain);
var identifier = Guid.NewGuid();
LoggedInUserIds.Add(identifier, adminUser.Id);
return identifier;
}
}
I'm keeping a record in my database to handle roles, so this class handles verifying with AD and fetching the user from the database:
public class AdminUserValidator : IAdminUserValidator
{
readonly IActiveDirectoryUserValidator _activeDirectoryUserValidator;
readonly IAdminUserFetcher _adminUserFetcher;
public AdminUserValidator(IAdminUserFetcher adminUserFetcher,
IActiveDirectoryUserValidator activeDirectoryUserValidator)
{
_adminUserFetcher = adminUserFetcher;
_activeDirectoryUserValidator = activeDirectoryUserValidator;
}
#region IAdminUserValidator Members
public AdminUser ValidateAndReturnAdminUser(string username, string clearTextPassword, string domain)
{
_activeDirectoryUserValidator.Validate(username, clearTextPassword, domain);
return _adminUserFetcher.GetAdminUser(1);
}
#endregion
}
And this class actually verifies that the username/password combination exist in Active Directory:
public class ActiveDirectoryUserValidator : IActiveDirectoryUserValidator
{
public void Validate(string username, string clearTextPassword, string domain)
{
using (var principalContext = new PrincipalContext(ContextType.Domain, domain))
{
// validate the credentials
bool isValid = principalContext.ValidateCredentials(username, clearTextPassword);
if (!isValid)
throw new Exception("Invalid username or password.");
}
}
}