Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Authentication and Authorization with ASP.NET Identity 2.0 for WCF Services

0.00/5 (No votes)
18 Sep 2016 3  
Real world WCF project structure, Authentication and Authorization

Introduction

This is an extension of my previous article "WCF for the Real World, Not Hello World, Part II", being focused on authentication based on ASP.NET Identity 2.x.

Background

"WCF for the Real World, Not Hello World" and "WCF for the Real World, Not Hello World, Part II" had demonstrated how to efficiently and effectively manage WCF service development in enterprise environment through team work in order to rapidly develop and deliver decent Web services.

Because authentication and authorization are important for basic security of Web services, it is better to run automatic integration tests often to assure such basic security is not broken.

Prerequisites

  • Visual Studio 2013/2015
  • xUnit through NuGet
  • xUnit runner for Visual Studio through NuGet
  • Fonlow Testing through NuGet

Hints

The DLL files of NuGet packages are included in the repository.

Authentication and Authorization with ASP.NET Identity 2.0

As of today in July 2014, there have been quite a lot of articles published about using ASP.NET MembershipProvider and RoleProvider in WCF services, however, there is little about using ASP.NET Identity 2.0.

ASP.NET and WCF had provided rich sets of security features, and there are varieties of choices for authentication and authorization. Here, I would like to introduce the simple one with username and password, since this is the default way in ASP.NET MVC and Web API.

If you are building ASP.NET MVC 5 applications with Identity 2.0 for B2C use cases and will provide related Web services to external developers for B2B use cases, you would probably want to share the same mechanism for related WCF services in your multi-tier architecture.

Assigning Roles to Operations

Through declarative programming, you could assign some roles to each service implementation:

public class Service1 : IService1
{
    public string GetData(int value)
    {
        System.Diagnostics.Debug.WriteLine("GetDataCalled");
        if (value == 666)
            throw new FaultException<Evil666Error>(new Evil666Error()
                                  { Message = "Hey, this is 666." });

        return string.Format("You entered: {0}", value);
    }

    [PrincipalPermission(SecurityAction.Demand, Role="Customer")]
    public CompositeType GetDataUsingDataContract(CompositeType composite)
    {

If you have done WCF authorization with RoleManager, you will see the code base is the same with PrincipalPermissionAttribute. In other words, your legacy codes depending on RoleManager don't need to be changed.

Adapting Identity Model in Codes

using System;
using System.IdentityModel.Selectors;
using System.ServiceModel;
using System.Security.Principal;
using System.Diagnostics;

//It is better to put the classes into a shared component
namespace Fonlow.Web.Identity
{
    public class IdentityValidator : UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            Debug.WriteLine("Check user name");
            if ((userName != "test") || (password != "tttttttt"))
            {
                var msg = String.Format("Unknown Username {0} or incorrect password {1}", 
                                         userName, password);
                Trace.TraceWarning(msg);
                throw new FaultException(msg);//the client actually will 
                       //receive MessageSecurityException. But if I throw MessageSecurityException, 
                       //the runtime will give FaultException to client without clear message.
            }
        }
    }

    public class RoleAuthorizationManager : ServiceAuthorizationManager
    {
        protected override bool CheckAccessCore(OperationContext operationContext)
        {
            // Find out the roleNames from the user database, for example, 
            // var roleNames = userManager.GetRoles(user.Id).ToArray();

            var roleNames = new string[] { "Customer" };

            operationContext.ServiceSecurityContext.AuthorizationContext.Properties["Principal"] = 
             new GenericPrincipal(operationContext.ServiceSecurityContext.PrimaryIdentity, roleNames);
            return true;
        }
    }
}

If you have your own user and role tables, it should be easy for you to replace the implementation, and you can even utilize ASP.NET Identity 2.0.

Wire Authentication and Authorization in Web.Config

<behaviors>
  <serviceBehaviors>
    <behavior name="authBehavior">
      <serviceCredentials>
        <userNameAuthentication userNamePasswordValidationMode="Custom"
         customUserNamePasswordValidatorType="Fonlow.Web.Identity.IdentityValidator,
         Fonlow.RealWorldImp" />
      </serviceCredentials>
      <serviceAuthorization principalPermissionMode="Custom"
       serviceAuthorizationManagerType="Fonlow.Web.Identity.RoleAuthorizationManager,
       Fonlow.RealWorldImp">

      </serviceAuthorization>

    </behavior>
  </serviceBehaviors>
</behaviors>

And this line wires the behavior to the service.

<service name="Fonlow.Demo.RealWorldService.Service1"
behaviorConfiguration="authBehavior">

As you can see, comparing with the usage of MembershipProvider and RoleProvider, the codes and the config become shorter and more elegant.

Adapting ASP.NET Identity 2.0

I would presume you have some skills in ASP.NET MVC 5 with ASP.NET Identity 2.0 and Entity Framework, so I won't repeat what you had already known and what is available in many articles.

public class IdentityValidator : UserNamePasswordValidator
{
    public override void Validate(string userName, string password)
    {
        using (var context = new IdentityDbContext())
        {
            using (var userManager = new UserManager<ApplicationUser>
                                (new UserStore<ApplicationUser>(context)))
            {
                var user = userManager.Find(userName, password);
                if (user == null)
                {
                    var msg = String.Format("Unknown Username {0}
                              or incorrect password {1}", userName, password);
                    Trace.TraceWarning(msg);
                    throw new FaultException(msg);//the client actually will receive
                        // MessageSecurityException. But if I throw MessageSecurityException,
                        // the runtime will give FaultException to client without clear message.
                }
            }
        }
    }
}

public class RoleAuthorizationManager : ServiceAuthorizationManager
{
    protected override bool CheckAccessCore(OperationContext operationContext)
    {
        using (var context = new IdentityDbContext())
        using (var userStore = new UserStore<ApplicationUser>(context))
        {
            using (var userManager = new UserManager<ApplicationUser>(userStore))
            {
                var identity =operationContext.ServiceSecurityContext.PrimaryIdentity;
                var user = userManager.FindByName(identity.Name);
                if (user == null)
                {
                    var msg = String.Format("Unknown Username {0} .", user.UserName);
                    Trace.TraceWarning(msg);
                    throw new FaultException(msg);
                }

                //Assign roles to the Principal property for runtime to match with
                //PrincipalPermissionAttributes decorated on the service operation.
                var roleNames = userManager.GetRoles(user.Id).ToArray();//users without
                        //any role assigned should then call operations not decorated
                        //by PrincipalPermissionAttributes
                operationContext.ServiceSecurityContext.AuthorizationContext.Properties
                     ["Principal"] = new GenericPrincipal
                     (operationContext.ServiceSecurityContext.PrimaryIdentity, roleNames);

                return true;
            }
        }
    }
}

As you can see, comparing 2 different implementations of class IdentifyValidator on IdentityModel, the whole structures are the same with or without Identity 2.0,

And you have probably recognized that IdentityDbContext is the DbContext class generated by VS IDE when creating the MVC 5 project. It doesn't sound elegant that your WCF projects depend on the MVC 5 project. So I would move the respective models and DbContext out of the MVC 5 project into a project called MyCompany.Security.Identity. Then the MVC 5 project and WCF projects could share the same auth mechanism.

Remarks

You won't see the above codes with Identity 2.0 in the demo codes attached in this article, since the extra dependencies on Identity 2.0, Entity Framework and the SQL database should be available in your MVC 5 project, and it is better to keep the demo codes small serving as demo.

And after you download and run the project, you may be seeing warning messages about SSL certificates, I will let you Google or SO to find out solutions. :)

Points of Interest

WCF and MVC are very sophisticated, comprehensive and mature, we should study more in depth, rather than write clumsy codes that work but generate huge technical debts.

Entity Framework and Identity 2.0 make things even better, so you could easily get rid of the clumsiness of legacy providers.

Obviously, it is not efficient to verify username and password through querying the db context per request, especially when the traffic is heavy. And there could be many ways to secure a WCF service in real world projects, while this article is just giving you a starting point for using Identity 2.0 in WCF.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here