Click here to Skip to main content
13,006,002 members (36,231 online)
Click here to Skip to main content
Add your own
alternative version

Stats

50K views
1.6K downloads
73 bookmarked
Posted 31 Jul 2014

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

, 18 Sep 2016
Rate this:
Please Sign up or sign in to vote.
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 works 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.

Prerequsites :

  • Visual Sutdio 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 lot articles published about using ASP.NET MembershipProvider and RoleProvider in WCF services, however, there is few 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 your 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 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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

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

You may also be interested in...

Comments and Discussions

 
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
memberMember 1152383921-Nov-15 8:20 
QuestionSystem.Security.Permissions.PrincipalPermission(SecurityAction.Demand, authenticated:=True) Pin
Bruno A. Zambetti7-Aug-15 6:38
memberBruno A. Zambetti7-Aug-15 6:38 
QuestionHow To Increase Stamina Pin
Ankur Pathania14-May-15 3:36
memberAnkur Pathania14-May-15 3:36 
QuestionAuthorize attribute Pin
Member 1126705228-Nov-14 10:30
memberMember 1126705228-Nov-14 10:30 
GeneralMy vote of 5 Pin
Humayun Kabir Mamun14-Aug-14 20:04
memberHumayun Kabir Mamun14-Aug-14 20:04 
QuestionPics are ok now Pin
L Hills4-Aug-14 1:41
memberL Hills4-Aug-14 1:41 
Questionpictures are missed Pin
Harun Çetin1-Aug-14 1:57
memberHarun Çetin1-Aug-14 1:57 
GeneralFigures not showing up Pin
BigTimber@home31-Jul-14 11:30
memberBigTimber@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.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170628.1 | Last Updated 18 Sep 2016
Article Copyright 2014 by Zijian
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid