Custom identity class to represent Dynamics CRM users in WCF services

A few weeks ago I wrote a post called "Custom WCF service authentication using Microsoft Dynamics CRM credentials" in which I showed how to secure Windows Communication Foundation (WCF) web services using Dynamics CRM usernames and passwords. In that post, I used a GenericIdentity object to store the CRM user information, but unfortunately the GenericIdentity class is extremely limited in the amount of user-related information it can hold, so in this post I will show how to create and use a custom identity object to represent CRM users. In addition to making it simple to store and use attributes like phone number, name and address, this will also allow you to easily enumerate a CRM user's roles or team assignments where necessary, which cannot be done directly with a GenericIdentity object.

Here's a visual depiction of how my custom WCF service authentication helps facilitate easier integration between external systems and a Dynamics CRM solution.

There are three things I want to cover in this post:

  1. The custom identity class
  2. Retrieving and storing user data in a custom identity object
  3. Accessing the stored user data

The custom identity class

The CrmIdentity class implements the System.Security.Principal.IIdentity interface. My class includes the following public properties and methods:

public string Name
public string Email
public string FirstName
public string LastName
public Guid UserId
public string[] Teams
public string[] Roles
public bool IsAuthenticated
public string AuthenticationType
public void SetAuthenticated(bool authFlag)
public void AddRole(string roleName)
public void RemoveRole(string roleName)
public bool InRole(string roleName)
public void AddTeam(string teamName)
public void RemoveTeam(string teamName)
public bool InTeam(string teamName)

You can download the complete CrmIdentity class
here.

Retrieving and storing the user data

I've updated the Validate method from my original post to store the retrieved user attributes and roles in a CrmIdentity object. (Retrieving and storing teams is left as an exercise for the reader.) The CrmIdentity object is then used to create a GenericPrincipal object that will be used in the actual service class. Here is the updated Validate method:

public override void Validate(string username, string password)
{
    //get the httpcontext so we can store the user guid for impersonation later
    HttpContext context = HttpContext.Current;
    //if username or password are null, obvs we can't continue
    if (null == username || null == password)
    {
        throw new ArgumentNullException();
    }
    //get the crm connection
    Microsoft.Xrm.Client.CrmConnection connection = CrmUtils.GetCrmConnection(username, password);
    //try the whoami request
    //if it fails (user can't be authenticated, is disabled, etc.), the client will get a soap fault message
    using (OrganizationService service = new OrganizationService(connection))
    {
        try
        {
            WhoAmIRequest req = new WhoAmIRequest();
            WhoAmIResponse resp = (WhoAmIResponse)service.Execute(req);
            Entity systemuser = CrmUtils.GetSystemUser(resp.UserId, service);
            CrmIdentity crmIdentity = new CrmIdentity();
            crmIdentity.Name = (string)systemuser["fullname"];
            crmIdentity.FirstName = (string)systemuser["firstname"];
            crmIdentity.LastName = (string)systemuser["lastname"];
            crmIdentity.Email = (string)systemuser["emailaddress1"];
            crmIdentity.UserId = resp.UserId;
            crmIdentity.SetAuthenticated(true);
            List roles = CrmUtils.GetUserRoles(resp.UserId, service);
            foreach (string role in roles)
            {
                crmIdentity.AddRole(role);
            }
            context.User = new GenericPrincipal(crmIdentity, roles.ToArray());
        }
        catch (System.ServiceModel.Security.MessageSecurityException ex)
        {
            throw new FaultException(ex.Message); 
        }
        catch (Exception ex)
        {
            throw new FaultException(ex.Message);
        }
    }
}

Accessing the stored user data

Finally, when you want to access the stored user data inside your service, you access the current request's HttpContext object and cast its User.Identity object as a CrmIdentity object like so:

HttpContext context = HttpContext.Current;
CrmIdentity crmIdentity = (CrmIdentity)context.User.Identity;

How do you handle authentication and authorization for your custom Dynamics CRM solution web services? Would you find it easier to use a completely separate authorization mechanism? Let us know in the comments!

A version of this post was originally published on the HP Enterprise Services Application Services blog.

comments powered by Disqus