Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I authenticate against Active Directory in Nancy?

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?

like image 845
Byron Sommardahl Avatar asked Sep 12 '12 17:09

Byron Sommardahl


1 Answers

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.");
        }

    }
}
like image 113
Byron Sommardahl Avatar answered Nov 10 '22 13:11

Byron Sommardahl