Click here to Skip to main content
Click here to Skip to main content

Active Directory / Forms Authentication / User.IsInRole

, 26 May 2009
Rate this:
Please Sign up or sign in to vote.
Getting User.IsInRole to work when using the Active Directory Provider through Forms Authentication

Introduction

Usually you just need to use:

  1. Integrated Windows authentication (uses Active Directory)
  2. OR Forms authentication

This is for the RARE case where you want to use the Active Directory provider in Forms Authentication.

Now implementing FormsAuthentication with the AD Provider isn't too bad, but .NET does not end up populating the Roles for the current user, so you can't use User.IsInRole.

The way around this is to create your own custom role provider. 

*********************************************************************
This is for ASP.NET 2.0. Not sure of it's needed in 3+ and it certainly won't work in 1.0. *********************************************************************

Background   

You'll need a solid understanding of Directory Services to get much out of this, since none of the code is commented.

Code

Web.Config

Ok, so the part that is custom here is the role manager. The type attribute should be the fully qualified name of your class. I just threw mine in the app_code directory, so it is just the class name. The admin user/password should be an active directory user that has access to do searches on active directory.

What you want to look at here is the roleManager. If you haven't done Forms Auth with AD, you might also want to look at the Connection String and the Membership Provider areas.

<?xml version="1.0"?>
<configuration>
    <connectionStrings>
	<add name="ADConnectionString" 
	connectionString="LDAP://yourhost.com/DC=host,DC=com" />
    </connectionStrings>
    <system.web>
        <roleManager enabled="true" cacheRolesInCookie="true" 
		defaultProvider="MyADRoleProvider" 
        cookieName=".ASPXROLES" cookiePath="/" cookieTimeout="30" 
	cookieRequireSSL="false" 
        cookieSlidingExpiration="true" createPersistentCookie="false" 
	cookieProtection="All">
	   <providers>
  	       <add name="MyADRoleProvider" 
                  type="CustomActiveDirectoryRoleProvider" 
		connectionStringName="ADConnectionString" 
		applicationName="/" 
		connectionUsername="adminUser" 
                  connectionPassword="adminPassword" />
	    </providers>
        </roleManager>

        <compilation debug="true">
	    <assemblies>
 	        <add assembly="System.DirectoryServices, 
		Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
	    </assemblies>
        </compilation>

        <membership defaultProvider="MembershipADProvider">
            <providers>
	       <add name="MembershipADProvider" 
	          type="System.Web.Security.ActiveDirectoryMembershipProvider, 
                   System.Web, Version=2.0.0.0, Culture=neutral, 
                   PublicKeyToken=b03f5f7f11d50a3a" 
	          connectionStringName="ADConnectionString" 
                   connectionUsername="adminUsername" 
                   connectionPassword="adminPassword" 
	          attributeMapUsername="sAMAccountName" />
            </providers>
        </membership>
        <authentication mode="Forms">
            <forms name=".ASPNET"/>
        </authentication>
        <authorization>
            <deny users="?"/>
        </authorization>
    </system.web>
</configuration> 

Here's the "impressive" Login.aspx page:

<asp:Login ID="Login1" runat="server" DisplayRememberMe="false">
</asp:Login>  

Alternatively, if you were trying to create a pass-through Login page, you can validate in this manner:

string username, password;
if (Membership.ValidateUser(username, password)) {
    FormsAuthentication.RedirectFromLoginPage(username, false);
} 

Next we'll see the provider. There are only a couple of methods you have to provide to get the User.IsInRole function to work. If you want to implement more, though, that's fine. Here's the basic required information for CustomActiveDirectoryRoleProvider.cs:

Note, this is handling nested membership, and it has to keep track of the groups it's done so it doesn't duplicate any.

using System;
using System.Collections;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Web.Configuration;
using System.Web.Security;

/// <summary>
/// Summary description for CustomActiveDirectoryRoleProvider
/// </summary>
public class CustomActiveDirectoryRoleProvider : RoleProvider
{
    
    string _connectionString = string.Empty;
    string _applicationName = string.Empty;
    string _userName = string.Empty;
    string _userPassword = string.Empty;

    public override void Initialize
	(string name, System.Collections.Specialized.NameValueCollection config) {
        _connectionString = config["connectionStringName"];

        if (!string.IsNullOrEmpty(config["applicationName"]))
            _applicationName = config["applicationName"];

        if (!string.IsNullOrEmpty(config["connectionUsername"]))
            _userName = config["connectionUsername"];

        if (!string.IsNullOrEmpty(config["connectionPassword"]))
            _userPassword = config["connectionPassword"];
        
        base.Initialize(name, config);
    }

    public override string ApplicationName {
        get {
            return _applicationName;
        }
        set {
            _applicationName = value;
        }
    }

    public override string[] GetRolesForUser(string userName) {
        userName = RemoveADGroup(userName);
        return GetUserRoles(userName);
    }

    string RemoveADGroup(string name) {
        string[] ary = name.Split(new char[] { '\\' });
        return ary[ary.Length - 1];
    }
    string[] GetUserRoles(string userName) {
        DirectoryEntry obEntry = new DirectoryEntry(
            WebConfigurationManager.ConnectionStrings
		[_connectionString].ConnectionString,
            	_userName, _userPassword);
        DirectorySearcher srch = new DirectorySearcher
		(obEntry, "(sAMAccountName=" + userName + ")");
        SearchResult res = srch.FindOne();

        Dictionary<string, string> dictionary = new Dictionary<string, string>();

        if (null != res) {
            DirectoryEntry obUser = new DirectoryEntry
			(res.Path, _userName, _userPassword);

            string rootPath = WebConfigurationManager.ConnectionStrings
				[_connectionString].ConnectionString;
            rootPath = rootPath.Substring(0,rootPath.LastIndexOf(@"/") + 1);

            GetMemberships(obUser, dictionary, rootPath);
        }
        string[] ary = new string[dictionary.Count];
        dictionary.Values.CopyTo(ary, 0);
        return ary;
    }

    void GetMemberships(DirectoryEntry entry, 
	Dictionary<string, string> dictionary, string rootPath) {
        List<DirectoryEntry> childrenToCheck = new List<DirectoryEntry>();
        PropertyValueCollection children = entry.Properties["memberOf"];
        foreach (string childDN in children) {
            if (!dictionary.ContainsKey(childDN)) {
                DirectoryEntry obGpEntry = 
		new DirectoryEntry(rootPath + childDN, _userName, _userPassword);
                string groupName = 
		obGpEntry.Properties["sAMAccountName"].Value.ToString();

                dictionary.Add(childDN, groupName);
                childrenToCheck.Add(obGpEntry);
            }
        }
        foreach (DirectoryEntry child in childrenToCheck) {
            GetMemberships(child, dictionary, rootPath);
        }
    }
    public override void AddUsersToRoles(string[] usernames, string[] roleNames) {
        throw new Exception("The method or operation is not implemented.");
    }

    public override void CreateRole(string roleName) {
        throw new Exception("The method or operation is not implemented.");
    }

    public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) {
        throw new Exception("The method or operation is not implemented.");
    }

    public override string[] FindUsersInRole(string roleName, string usernameToMatch) {
        throw new Exception("The method or operation is not implemented.");
    }

    public override string[] GetAllRoles() {
        throw new Exception("The method or operation is not implemented.");
    }

    public override string[] GetUsersInRole(string roleName) {
        throw new Exception("The method or operation is not implemented.");
    }

    public override bool IsUserInRole(string username, string roleName) {
        throw new Exception("The method or operation is not implemented.");
    }

    public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) {
        throw new Exception("The method or operation is not implemented.");
    }

    public override bool RoleExists(string roleName) {
        throw new Exception("The method or operation is not implemented.");
    }
}

Points of Interest

Here are a couple of the other methods that I implemented before I realized that I didn't need to use them unless I was going to be using the Roles methods (i.e.: Roles.GetUsersInRole("blah\somerole")). They do work... you just don't need them for the normal Page.User.IsInRole("blah\basdfasf");

    public override string[] GetUsersInRole(string roleName) {
        return GetGroupMembers(RemoveADGroup(roleName));
    }

    public override bool IsUserInRole(string username, string roleName) {
        string[] ary = GetRolesForUser(username);
        foreach (string s in ary) {
            if (roleName.ToLower() == s.ToLower())
                return true;
        }
        return false;
    }

    string[] GetGroupMembers(string groupName) {
        DirectoryEntry obEntry = new DirectoryEntry(
            WebConfigurationManager.ConnectionStrings[_connectionString].ConnectionString,
            _userName, _userPassword);
        DirectorySearcher srch = new DirectorySearcher
			(obEntry, "(sAMAccountName=" + groupName + ")");
        SearchResult res = srch.FindOne();

        Dictionary<string, string> groups = new Dictionary<string, string>();
        Dictionary<string, string> members = new Dictionary<string, string>();

        if (null != res) {
            DirectoryEntry entry = new DirectoryEntry(res.Path, _userName, _userPassword);

            GetMembers(entry, groups, members);
        }
        string[] ary = new string[members.Count];
        members.Values.CopyTo(ary, 0);
        return ary;
    }

    void GetMembers(DirectoryEntry entry, 
	Dictionary<string, string> groups, Dictionary<string, string> members) {
        List<DirectoryEntry> childrenToCheck = new List<DirectoryEntry>();
        object children = entry.Invoke("Members", null);

        foreach (object childObject in (IEnumerable)children) {
            DirectoryEntry child = new DirectoryEntry(childObject);
            string type = getEntryType(child);
            if (type == "G" && !groups.ContainsKey(child.Path)) {
                childrenToCheck.Add(child);
            } else if (type == "P" && !members.ContainsKey(child.Path)) {
                members.Add(child.Path, child.Properties
			["sAMAccountName"].Value.ToString());
            }
        }
        foreach (DirectoryEntry child in childrenToCheck) {
            GetMembers(child, groups, members);
        }
    }

    string getEntryType(DirectoryEntry inEntry) {
        if (inEntry.Properties.Contains("objectCategory")) {
            string fullValue = inEntry.Properties["objectCategory"].Value.ToString();
            if (fullValue.StartsWith("CN=Group"))
                return "G";
            else if (fullValue.StartsWith("CN=Person"))
                return "P";
        }
        return "";
    }

References

  • This site provided me insight on the custom provider, although I changed the access methods to handle nested groups.
  • This link gives you the basics of Forms Authentication with Active Directory (MSDN).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

ColinBashBash
Software Developer
United States United States
likes boardgames, computer games, and enjoys his .net programming job.

Comments and Discussions

 
GeneralNot a bad start, but PinmemberAcoustic28-May-09 7:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 26 May 2009
Article Copyright 2009 by ColinBashBash
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid