Working with multiple membership and role providers

Jul 16, 2008, 23:39 PM
Relates to
3.2 SP2 Hotfix (1616)
Related articles
Related file
Keywords
membership, provider, role, security, AD
Compatibility
Categories
  • Sitefinity 3.x
Tags
  • Security


HOW TO
Set a custom pair Membership/Role provider and use them for the public part of the website while using the Telerik AD membership/role providers for the administration.


DESCRIPTION

Sitefinity supports multiple membership providers, giving websites the power to use more than one data source for users.
Let us distinguish between two types of users in our test website:
1. Public users are users who are not able to log in the administration. They are able to subscribe, post in forums, visit restricted pages, etc.
2. CMS users are the ones who moderate the website. They are capable of editing pages, managing content items, etc.

In this example the Active Directory providers shipped with Sitefinity are used for the CMS Administration. This way the domain groups and users are considered as Sitefinity roles and users and permissions are assigned to them.

The public users in the example come from “custom” membership and role providers, the ones included in the .NET Framework: 
System.Web.Security.SqlMembershipProvider
System.Web.Security.SqlRoleProvider


Note: All the modified files needed for this example are provided in the attached Multiple_Providers.zip archive. In order to run the example, provide valid user credentials in the web.config and replace those files in an empty Sitefinity website.


SOLUTION

I. ADDING THE PROVIDERS
The first step when using custom membership and role providers is to add the custom providers to the application. You can this either by adding the classes directly to the App_Code folder or by adding a reference. In our case, we don’t need this because the “custom” providers are included in the framework.

II. CONFIGURATION FILE
Next, you need to define the membership and role providers in the application web.config file:

<roleManager enabled="true" cacheRolesInCookie="true" defaultProvider="SqlProvider">  
  <providers> 
  <clear /> 
    <add  
        name="SqlProvider" 
        type="System.Web.Security.SqlRoleProvider" 
              connectionStringName="SqlServices" 
              applicationName="/" /> 
        <add  
          name="AD" 
          domainName="192.168.0.171" 
          connectionStringName="AD" 
          connectionUsername="DOMAIN\you" 
          connectionPassword="your cat’s name" 
          groupMaps="Sitefinity, Sales" 
          type="Telerik.Security.ActiveDirectory.TelerikADRoleProvider, Telerik.Security" /> 
      </providers> 
    </roleManager> 
 
    <membership defaultProvider="SqlProvider" userIsOnlineTimeWindow="15" hashAlgorithmType="">  
      <providers> 
        <clear /> 
        <add  
          name="SqlProvider" 
          type="System.Web.Security.SqlMembershipProvider" 
          connectionStringName="SqlServices" 
          applicationName="/" 
          enablePasswordRetrieval="false" 
          enablePasswordReset="true" 
          requiresQuestionAndAnswer="false" 
          requiresUniqueEmail="false" 
          passwordFormat="Hashed" 
          maxInvalidPasswordAttempts="10" 
          passwordAttemptWindow="10" 
          passwordStrengthRegularExpression="" 
          minRequiredPasswordLength="1" 
          minRequiredNonalphanumericCharacters="0" 
          /> 
        <add name="AD" 
             connectionStringName="AD" 
             enableSearchMethods="true" 
             attributeMapUsername="sAMAccountName" 
             connectionUsername="DOMAIN\you" 
             connectionPassword="your cat’s name" 
             type="Telerik.Security.ActiveDirectory.TelerikADMembershipProvider, Telerik.Security" />          
      </providers> 
    </membership> 
 
 

In the above code, we defined membership and role providers for the test application. The default providers are named “SqlProviders” and are used for the public part, while the Active Directory providers are used for the administration. 

We use the groupMaps property to specify which domain groups to be used as roles in Sitefinity. In our domain there are groups: Sitefinity, Sales, Webteam and our administrators use them to give permissions for some shared resources. We are going to use “Sitefinity” and “Sales” as roles in Sitefinity.

Note: Sitefinity demands the membership and the role provider to have the same name!

Marked in yellow are the two connection strings we need to add in order to get the example to work:

<connectionStrings> 
    <add name="Sitefinity" connectionString="Data Source=.\SQLExpress;Integrated Security=True;User Instance=True;AttachDBFilename=|DataDirectory|Sitefinity.mdf" 
      providerName="System.Data.SqlClient" /> 
    <add name="SqlServices" connectionString="Data Source=.\SQLExpress;Integrated Security=SSPI;User Instance=True;AttachDBFilename=|DataDirectory|aspnetdb.mdf" /> 
    <add name="AD" connectionString="LDAP://192.168.0.171" /> 
</connectionStrings> 

In the above code we are using an internal IP address for the LDAP connection but this would also work with ldap://telerik.com/ or any other valid LDAP connection string.

For the SQL providers we use the SQL Express database aspnetdb.mdf. If this database does not exist, it will be automatically created with the needed tables and procedures.

So far we have defined the membership and role providers, however, we are still not ready to run the website, as we haven’t specified the providers, which Sitefinity should use. This should be done in the telerik section of the application web.config:

    <security defaultProvider="DefaultSecurityProvider" cmsProvidersName="AD">  
      <roles> 
        <clear /> 
        <add name="Sitefinity" permission="Unrestricted" /> 
      </roles> 
      <providers> 
        <clear /> 
        <add name="DefaultSecurityProvider" connectionStringName="DefaultConnection"   
             type="Telerik.Security.Data.DefaultSecurityProvider, Telerik.Security.Data"   
             membershipProvider="AD" roleProvider="AD" /> 
      </providers> 
    </security> 

The cmsProvidersName attribute specifies which providers should be used by the UserManager, while the membershipProvider and roleProvider specify which providers to be used with the Default Security Provider.

Note: They should all use the same name, in our example it is “AD”.

Unrestricted permissions are given for the role that has unlimited rights no matter what permissions are set. By default, a new website comes with the role “Administrators” as unrestricted:

<add name="Administrators" permission="Unrestricted" /> 
 

Although no permissions are set for this role, users in it have unlimited permissions.

In our case, we are no longer using the default role provider, so the “Administrators” role does not exist. The roles we can use are “Sitefinity” and “Sales” and we decide to give administrator permissions to the "Sitefinity" role.

The next step would be to run the website and log in as a member of this “Sitefinity” role (this role can be different for different domains, in our case I need unrestricted permissions to create the website and I am a member of the “Sitefinity” role).

III. SITEFINITY ADMINISTRATION
Having the configuration done, we can now continue with the Sitefinity administration. When logging in as a member of the “Sitefinity” role (in my case user: kiprov), we see that both provider pairs are available at the Administration > Users tab:

Providers

Users and roles can be created for the SQL providers, while the Active Directory ones are read-only. For testing purposes let’s create a user for the Sql providers:

username: Jon
password: Jon

This user is not currently assigned to any role, so he is just a member of “Everyone”. If we give permissions for “Everyone”, they will be valid for Jon.

For testing purposes, we have created the following pages:

Sitemap

• home is public and accessible for anyone who visits the website
• restricted is accessible only for users who are logged in. To do this, we set “Anonymous access” to Deny and grant the View permission to role “Everyone”.
• under_restricted is again accessible only for users who are logged in. We don’t set anything for it, because both permissions, and “Anonymous access” property are inherited.
• for_sales_only is accessible only for users who are logged in with the AD provider (CMS users) and belong to the “Sales” role. The View permissions for role “Everyone” is not set, only role "Sales" the View permission is set to “Allow”
 for_administrators_only is accessible only for unrestricted users. To do this, we have to set the View permission to Deny for role "Everyone".
• admin_login is a page that provides a login form for the Active Directory users (we’ll discuss the login forms later)
• sql_login is a page that provides a login form for the SQL users

IV. LOGIN FORMS
The login page that comes with Sitefinity is responsible for the authentication of CMS users. It works with the membership and role providers that use the UserManager class uses (see above), in our case these are the “AD” providers.
 
However, a question emerges: What happens if we have the same user in both data sources?

In our example, this would happen if we have a user “kiprov” in the SQL datasource (or Jon in the AD). Then, if a user logs in as “kiprov” in the login form, this would be the user from AD. If we add another login control and specify that it should use the SQL membership provider, then, when “kiprov” logs in through this login form, he’ll be authenticated as the SQL user. These two users could have different passwords, different emails, and what is critical, different roles.

Note: When a login form in ASP.NET authenticates a user, the default role provider is taken as the role provider for this user. That is why in the example we are using SqlProvider as the default ASP.NET role provider. If we used AD as the default provider and a user logs in, here is what would happen:
1. User types his credentials (kiprov).
2. The login control authenticates the user against the specified membership provider (SqlProvider).
3. A new role principal is created and the default role provider is used (AD)!
4. Then the user (from the SQL source) is mistaken with the one from the domain, he benefits from all the Windows groups/roles (i.e. “Sitefinity” or “Sales”) and in our case, gains unrestricted privileges.

In Sitefinity SP2 Hotfix 1616 the permissions work with only one role provider. So permissions for pages, modules, etc can be set only for the CMS role provider, in our case it is “AD”.

For the restricted pages it is important to know which role provider is valid for the current user. In order to store the role provider name, the Sitefinity login page uses a data field from the authentication ticket. This way when a page is restricted, first it checks if the current user is a CMS user, i.e. has roles and permissions in the CMS, or a public user (his roles are not considered in any way by Sitefinity).

Here is how the login form sets the name of the role provider in a field in the authentication cookie:

HttpCookie cookie =  
   this.Response.Cookies[FormsAuthentication.FormsCookieName];  
UserManager.Default.SetAuthenticationCookie(cookie); 

Here the SetAuthenticationCookie method writes the default role provider for Sitefinity in the authentication cookie.

Having said this, we can continue with creating a custom login page for the CMS users (these are the ones able to log in the administration). Here is part of the code which does that:

void Login1_LoggedIn(object sender, EventArgs e)  
  {  
        HttpCookie cookie = this.Response.Cookies[FormsAuthentication.FormsCookieName];  
        if (!String.IsNullOrEmpty(Login1.MembershipProvider))  
        {  
            UserManager man = new   
   UserManager(Login1.MembershipProvider);  
            man.SetAuthenticationCookie(cookie);  
        }  
        else 
            UserManager.Default.SetAuthenticationCookie(cookie);  
 
…  
 

In the provided example we did this in the login control from the toolbox: ~/Sitefinity/UserControls/Login/LoginControl.ascx.

The only thing that remains is to add the login control from the toolbox to the admin_login.aspx page and set its membership provider to “AD”.

We can use the same control in the sql_login page, we only need to change the membership provider to “SqlProvider”.
V. PERMISSIONS

Good to know: Permissions for pages are stored in the sf_PagePermission table, the global ones are stored in sf_GlobalPermission and those for the modules are stored in sf_SecPermission table.

So now, except the default one, we have two custom login forms, one for each membership provider. CMS users are able to authenticate through admin_login.aspx as well as the default /Sitefinity/login.aspx, while public (SQL) users are able to authenticate through the sql_login page.

If our user Jon authenticates through the SQL login, he is now able to see the restricted pages in the site navigation controls. However, he is also able to navigate to the restricted pages that are available only for the “Sales” role. That is because Sitefinity does not use his roles, so permissions are not checked and the only thing that is applied is the “Anonymous access” property. Here is a KB article that explains how you can customize this behavior:
How to restrict public user access to a page

CMS users who are authenticated by the admin_login form can access only the restricted pages they have access to. For instance, a user “George” can view the restricted.aspx page. If he is a member of the "Sales" role, he’ll be able to see the page for_sales_only.aspx. Moreover, if he is a member of an unrestricted role (in our case “Sitefinity”), he will be able to visit the page for_administrators_only.aspx.

Useful links: 

Developer Manual
User Manual,section 15 and 16
Telerik Active Directory Role provider
How to restrict public user access to a page