Converting the CRM 2011 Customer Portal to use Forms Authentication and the ASP.NET SqlMembershipProvider
Tags: ASPNETdb, CRM 2011, customer portal, SqlMembershipProvider
Out of the box, the CRM 2011 Customer Portal is integrated with LiveID authentication. While this is a great example of an integrated authentication provider, it may not be the ideal solution for your business and it’s certainly a tricky configuration to get working in your debug environment. All is not lost. It’s a very simple process to convert the out-of-the-box Customer Portal to use Forms Authentication and the SqlMembershipProvider.
For this tutorial, we are assuming that you have the CRM 2011 Customer Portal installed and configured with a CRM 2011 instance. There are a number of great blogs and tutorials on the .NET Authentication Providers and Shan McArthur has an excellent tutorial on implementing Active Directory Authentication with the Customer Portal 2011. They all based around swapping out the Membership Provider so let’s get started.
If you wish to see a very simple SqlMembershipProvider implementation in action, all you need to do is create a new ASP.NET Web Application in VS2010. The template solution has everything you need to create, login and manage a secured web app. It’s this simple template that we are going to use to convert the CRM 2011 Customer Portal to use the SqlMembershipProvider.
First things first, removing the LiveID references.
Take a look at the Customer Portal Login page (/Pages/Login.aspx). The Login control on the form uses the LiveIdLoginStatus,
remove this and replace it with an ASP Login control straight from the toolbox
Like the other authentication methods we need to do a bit of work in the CodeBehind and here we need to remove the redirection calls to the LiveID services.
{
if ((User != null) && User.Identity.IsAuthenticated)
{
…
else
{
//var inviteQueryString = string.IsNullOrEmpty(Request[“InvitationCode”]) ? string.Empty : “?InvitationCode=” + Request[“InvitationCode”];
//if (page == null)
//{
// throw new Exception(“Please contact your System Administrator. Required Site Marker titled ‘Live User Registration’ is missing.”);
//}
//var registrationUrl = ServiceContext.GetUrl(page);
//LiveIdLink.RegistrationUrl = registrationUrl + inviteQueryString;
}
This Login page now behaves in the following way. If we request the Login page, and we are authenticated, then proceed to the ReturnUrl specified in the QueryString. If we’re not authenticated, then render the page with the Login control.
Set up an Authentication Provider
- Select “Security” and follow the “Security Setup Wizard” wizard to create an AspNetSqlProvider.
- When prompted, enable the role provider. We are going to use the CRM Access Permissions but SqlMembershipProvider needs to see the <rolemanager> explicitly enabled.
- When you’re prompted to create a user, set up a test account, for example “testuser1” and a suitable password. We will make use of this account later.
Back in the web.config, the LiveID implementation uses a CrmMembershipProvider to perform the authentication process
<providers>
<add name=”CrmMembershipProvider” type=”Microsoft.Xrm.Portal.Web.Security.LiveIdMembershipProvider, Microsoft.Xrm.Portal” liveIdConnectionStringName=”Live”/>
</providers>
</membership>
A straight replacement with the section below gives us the same functionality using the ASPNETdb
<providers>
<clear/>
<add name=”AspNetSqlMembershipProvider” type=”System.Web.Security.SqlMembershipProvider” connectionStringName=”ASPNETAuth”
enablePasswordRetrieval=”false” enablePasswordReset=”true” requiresQuestionAndAnswer=”false” requiresUniqueEmail=”false”
maxInvalidPasswordAttempts=”5″ minRequiredPasswordLength=”6″ minRequiredNonalphanumericCharacters=”0″ passwordAttemptWindow=”10″
applicationName=”/” />
</providers>
</membership>
The connection string to the ASPNETDB is held in a connnectionstring property ‘ASPNETAuth’. Add this to the <connectionStrings> section of your web.config
<add name=”ASPNETAuth”
connectionString=”data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnetdb.mdf;User Instance=true”
providerName=”System.Data.SqlClient” />
Our <rolemanager> remains unchanged; we still want to use the CrmContactRoleProvider and the Access Permissions schema already defined in the Customer Portal solution.
Finally, the Login Status control
When you browse to your portal, you may expect to see a “Login” link in the top right of the browser window and of course once you’re logged in this would be replaced with your name and a “Logout” link. This functionality is provided by the ASP LoginView which is declared in the /MasterPages/Default.master markup.
The LiveID LoginStatus control is no longer needed so cut this out
<crm:LiveIdLoginStatus ID=”LiveLoginStatus” runat=”server” LoginNavigateUrl='<%$ CrmSiteMap: SiteMarker=Login, Eval=Url %>’/>
and replace it with the ASPNET LoginStatus control.
<asp:LoginStatus ID=”LoginStatus1″ runat=”server” />
The LoginStatus control relies on the <authentication> configuration to find the path to the login page will need to be configured in the web.config as below.
<authentication mode=”Forms”>
<forms loginUrl=”/login” timeout=”525600″ />
</authentication>
The story so far.
Now we are fully integrated for ASPNETdb authentication but we still need a little bit of work to tie this up with the CRM application. If you run the application now and log in as testuser1, the provider will authenticate the credentials but the portal will have no idea which CRM Contact testuser1 is. Indeed, the functionality of the Customer Portal will not operate correctly; “testuser1” is not tied up with a CRM Contact so any records created or data retrieved will not be linked.
What we need now is a relationship between the membership provider and the CRM Contact. To do this we will let the membership provider do the authenticating and then when it’s completed we will find the appropriate CRM Contact.
Once the SqlMembershipProvider has done its work, the ASPNET Login control is configured to call the event handler OnLoggedIn=”Login1_LoggedIn”. Add the stub for this method into Login.cs
protected void Login1_LoggedIn(object sender, EventArgs e){
}
The OnLoggedIn event is only fired when authentication is successful. For unsuccessful attempts, you could implement OnLoginError=”Login1_LoginError” and its handler to notify the user that they’re login has failed.
Once we are successfully authenticated, we want to find the corresponding CRM Contact based on the username. We will re-use the schema of the Customer Portal and link the username field of the SqlAuthenticationProvider with the adx_username field of the CRM Contact.
The function LoginContact below uses the XrmContext.ContactSet to find the Contact record with a matching username. Don’t forget to add the Xrm and Linq namespaces to the top of the class file.
using System.Linq;
private Contact _loginContact;
protected Contact LoginContact
{
get
{
return _loginContact ??
(_loginContact = XrmContext.ContactSet
.FirstOrDefault(c => c.Adx_username == Login1.UserName));
}
}
We can now use LoginContact in our Login1_LoggedIn event handler to confirm that the CRM Contact exists.
protected void Login1_LoggedIn(object sender, EventArgs e){
if (LoginContact == null)
{
//CRM doesn’t have a matching username, Redirect to a Failed login page
}
else
{
// Everything is fine, maybe update the last login date here
}
}
Tie the whole thing together
Fire up your Customer Portal solution, click Login, enter the user details you set up earlier and away you go.
Whats next…
This implementation is as simple as it gets. There’s no support for managing portal accounts or keeping CRM and the membership provider in sync. In addition, creating and enabling CRM Contacts for portal access is geared around an invitation process. What if we want our portal to be completely self-servicing?
Next time, we will be adding the support for registering and maintaining a portal account, creating the CRM Contact and gluing the whole thing together.