Click here to Skip to main content
15,867,771 members
Articles / Web Development / HTML

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

Rate me:
Please Sign up or sign in to vote.
4.85/5 (24 votes)
18 Sep 2016CPOL4 min read 83.2K   1.9K   75   10
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:

C#
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

C#
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

XML
<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.

XML
<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.

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Australia Australia
I started my IT career in programming on different embedded devices since 1992, such as credit card readers, smart card readers and Palm Pilot.

Since 2000, I have mostly been developing business applications on Windows platforms while also developing some tools for myself and developers around the world, so we developers could focus more on delivering business values rather than repetitive tasks of handling technical details.

Beside technical works, I enjoy reading literatures, playing balls, cooking and gardening.

Comments and Discussions

 
PraiseThank you so much Pin
Allwin Bose26-Jul-17 1:01
Allwin Bose26-Jul-17 1:01 
QuestionError when i call services Pin
nemarc21-Jun-16 5:41
professionalnemarc21-Jun-16 5:41 
QuestionWCF Identity integration + MVC 5 Pin
Member 1152383921-Nov-15 8:20
Member 1152383921-Nov-15 8:20 
QuestionSystem.Security.Permissions.PrincipalPermission(SecurityAction.Demand, authenticated:=True) Pin
BAZ@Huge7-Aug-15 6:38
BAZ@Huge7-Aug-15 6:38 
QuestionHow To Increase Stamina Pin
Ankur Pathania14-May-15 3:36
Ankur Pathania14-May-15 3:36 
QuestionAuthorize attribute Pin
Member 1126705228-Nov-14 10:30
Member 1126705228-Nov-14 10:30 
GeneralMy vote of 5 Pin
Humayun Kabir Mamun14-Aug-14 20:04
Humayun Kabir Mamun14-Aug-14 20:04 
QuestionPics are ok now Pin
L Hills4-Aug-14 1:41
L Hills4-Aug-14 1:41 
Questionpictures are missed Pin
Harun Çetin1-Aug-14 1:57
Harun Çetin1-Aug-14 1:57 
GeneralFigures not showing up Pin
BigTimber@home31-Jul-14 11:30
professionalBigTimber@home31-Jul-14 11:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.