How can one authenticate against Active Directory? This question recently came up in our forum. I answered that the ActiveDirectoryMembershipProvider class is a full-service provider for authentication for Active Directory. But that lead to other questions: how does one 'register' the provider correctly? Can 2 providers be active at the same time? How can users from different domains be authenticated? Realizing that this was only the beginning, and that the forum would not be the best position to fend off the barrage, I retired, promising to cover the matter(s) in my blog. Here is the keeping of that promise.
So, our task is to authenticate users of a site through Active Directory. Usually, this issue comes up when a website is being created for an organization that has Active Directory deployed.
So, let’s get to it. We launch .NET Forge Community. In the configuration wizard, we mark 'Do not install a site solution' – we’ll start from a clean slate.
Note: ActiveDirectoryMembershipProvider requires the Full Trust level settings.
Strict security policy is required for сorporate a corporate website. We’ll disable access to anonymous users. We choose the 'Section(Folder) Access' point in the 'Edit Section' menu and revoke the 'Execute executable files' from the 'All Users' and grant that only to the 'Registered Users'.
To authenticate users in the site root, we create an entry page (login.aspx), setting only the Authentication on it (system.auth) with the default settings. We open this page to anonymous users by choosing 'Access' in the 'Edit Page' menu and grant the 'Execute executable files' to the 'All Users'. Now 'strangers' will not be permitted beyond the opening page of the site.
Now we’ll activate the authentication provider for Active Directory. This requires editing the web.config. Add a connection string to Active Directory in the connectionStrings.
<add name="BXConnectionString" connectionString="Data Source=.\sql;Initial Catalog=fce;User ID=sa;Password=password" />
<add name="NetForgeConnectionString" connectionString="LDAP://dc.netforge.loc/CN=Users,DC=netforge,DC=loc" />
<!-- name – we will use it later; -->
<!-- connectionString – LDAP Connection String. -->
Determining the connectionString parameter may present some difficulties, so we will spend some time on this. The format of a connection string is LDAP://server/dn. As you see, it’s in URI format, starting with the definition of the protocol name LDAP (in all-caps). Then server – this is the name of the server, domain or IP address. In the documentation, there is a strong recommendation to use the fully qualified domain name or FQDN. The next item, dc.netforge.loc, is the server dc in the domain netforge.loc. The connection is usually set on port 389. If your Active Directory server is set on a different port, you will need to enter the point number (for example: dc.netforge.loc:377). Lastly, we have dn – the distinguished name of the object, which is comprised of a list of 'relative distinguished names', separated by commas. A relative distinguished name has the form NameAttribute=Value. CN=User,DC=netforge,DC=loc – the group Users (attribute CN – container), domain netforge.loc (attributer DC – domain component).
Now we create a new authentication provider, which will be a user created using the connection string to Active Directory.
<!-- omitted code -->
<membership defaultProvider="BXSqlMembershipProvider" userIsOnlineTimeWindow="15">
<add name="BXSqlMembershipProvider" type="Bitrix.Security.BXSqlMembershipProvider" connectionStringName="BXConnectionString" enablePasswordRetrieval="false" enablePasswordReset="true" requiresCheckWord="true" maxInvalidPasswordAttempts="2147483647" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="6" requiresQuestionAndAnswer="false" />
<add name="NetForgeMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider" connectionStringName="NetForgeConnectionString" connectionUsername="NETFORGE\admin" connectionPassword="password" />
<!-- type - type of ActiveDirectoryMembershipProvider; -->
<!-- connectionStringName – name of LDAP Connection String that we have added in connectionStrings section; -->
<!-- connectionUsername – name of account with appropriate privileges; -->
<!-- connectionPassword – password of account specified in connectionUsername. -->
Note: connectionUsername and connectionPassword have to be supplied in plain text. If domain admin account is specified, you should encrypt this configuration section along with the connectionStrings section.
Please note that the BXSqlMembershipProvider hasn't been removed. It is done to allow the built-in administrator to log in. The Authentication system will perform authentication against each provider sequentially. It will stop on success or at end of the sequence. That is why order is important. In our example, ActiveDirectoryMembershipProvider goes after BXSqlMembershipProvider due to performance considerations. In my test environment BXSqlMembershipProvider responds much faster than ActiveDirectoryMembershipProvider.
Let’s test our solution. We open the site and try to enter as a user zg of the netforge.loc domain . The user name is in the format firstname.lastname@example.org – is the UPS (User Principal Name).
Login has been successful. But where did the user whose name is now displayed in the Authorization Form component (system.login) come from? We, of course, did not create it.
We enter the site under a built-in administrator user account (it was important to leave the SQL provider untouched) and check the list of site users (~/bitrix/admin/AuthUsersList.aspx). Indeed, there is a new user with a name that coincides with the domain account. So it is clear that it was created automatically after successful authentication by the Active Directory provider.
So what advantages have we got here? The source code requires no changes and will work normally after membership provider change. A call to BXIdentity.User property will return an instance of BXUser as before.
'Savvy', a reader asks: 'What if I want handle user names in the form DOMAIN\user, that is, in SAM (Security Account Manager) format?' By default, the Active Directory provider works with names in the UPN (User Principal Name) format. To use SAM format, simply add the attributeMapUserName attribute in the provider definition tag with the value SAMAccountName. our case, you can then enter the system under the name NETFORGE\zg.
There’s one last question: how to authenticate users from several domains? Do you already know the answer? Probably so – you simply add the needed providers as in our example. But remember that receiving a response from an Active Directory provider takes a certain amount of time. If the provider which confirms the identity of the user is at the end of a long list of Active Directory providers, the authentication process will be noticeably long. Of course, this is true only for the first login – afterward the binding to the provider will be saved.
So that’s all. Now we know how to authenticate users through Active Directory. In this article the only thing left is the issue of authentication by role. The site features user roles, but does not know anything about Active Directory groups. I intend to address this topic in one of the next articles.
Was the time spent reading this article well-spent? In any case, please leave commentary and ask questions. I’ll be happy to get feedback.
- ActiveDirectoryMembershipProvider Class
- How To: Use Membership in ASP.NET 2.0
- How To: Use Forms Authentication with Active Directory in Multiple Domains
- Walkthrough: Encrypting Configuration Information Using Protected Configuration