Click here to Skip to main content
12,449,440 members (60,981 online)
Click here to Skip to main content
Add your own
alternative version

Stats

39.9K views
1.4K downloads
66 bookmarked
Posted

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

, 14 Feb 2016 CPOL
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". So I would presume you have read it before reading this or got used to similar project structures, typically, for each service, you would establish 3 assemblies for contracts, implementation and client API.

Background

"WCF for the Real World, Not Hello World" 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. And the article had also explained WHY in addition to HOW.

This article covers the following:

  1. Construct integration tests using xUnit and IIS Express.
  2. Introduce authentication and authorization though ASP.NET Identity 2.0.

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.

Make Integration Tests Easier

I would like to run integration tests either in IDE or in a standalone runner, and the services should be launched and stopped automatically, without the need of running things as Administrator.

Basic Project Structure

Project sturcture

In addition to service codes in folder RealWorldServices, I create a WCF Application project "WcfService1" to contain all service assemblies, for example, RealWorldService and RealWorldImp. Please notice that I had deleted all the scaffolding C# codes created by IDE, so this project is just a thin "main program"/booster/facade to launch services in IDE and IIS Express as well as IIS. As you might had seen in  "WCF for the Real World, Not Hello World"  , having a WCF Application project is not mandatory for hosting services in IIS

Steps:

  1. Create a WCF Application project.
  2. Remove all C# codes generated by IDE.
  3. Add references to WCF service projects, for example RealWorldService and RealWorldImp.

The Web.config is telling IIS to activate the services without using SVC files:

<system.serviceModel>
  <serviceHostingEnvironment>
    <serviceActivations>
      <!--This is to replace the standalone svc file whic is the legacy of asp.net web app.-->
      <add relativeAddress = "RealWorldServices/RealWorld.svc" service = "Fonlow.Demo.RealWorldService.Service1"/>
    </serviceActivations>
  </serviceHostingEnvironment>
  <services>
    <service name="Fonlow.Demo.RealWorldService.Service1" behaviorConfiguration="authBehavior">

      <!-- Service Endpoints. A Service may provide multiple endpoints -->
      <!-- Not need to define host. Relative  -->
      <endpoint address="" binding="basicHttpsBinding" contract="Fonlow.Demo.RealWorldService.IService1" bindingConfiguration="httpsBindingConfig">
        <!--
            Upon deployment, the following identity element should be removed or replaced to reflect the
            identity under which the deployed service runs.  If removed, WCF will infer an appropriate identity
            automatically.
        -->
        <identity>
          <dns value="localhost" />
        </identity>
      </endpoint>

When you run WcfService1, it will be launched by IIS Express by default, and the services mentioned in web.config will be activated.

When you are developing more services, you may continue to add references to the WcfService1 project.

Remarks:

  • Adding references to new service implementation assemblies is just for intellisense when modifying Web.config in Visual Studio IDE. For production, It is perfectly OK that the assembly of the WCF application has no static binding to service assemblies. The service host will read the Web.config during startup and load respective service assemblies at runtime. In production, you don't even need to deploy the dummy WCF application assembly.
  • To support httpS when testing in IIS Express, you need some twists in the properties of the WCF application project, as documented in Scott Hanselman's article "Working with SSL at Development Time is easier with IIS Express". Please note there's a "magic" starting port number 44300 for httpS.

More Robust Client Proxies

If you had done WCF programming long enough, likely you have encountered this error message on client side without disclosing specific/useful error info:

"The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state."

This often happens when an exception is thrown on the service side by the run time rather than your service operation codes.

MSDN had suggested not to use the using statement with the client proxy class, even though the generated proxy classes had implemented the IDisposable interface. So this workaround does not conform to the programming rule in MSDN; or in other words, had made an exceptional case against the rule. While working well, the workaround might make your application codes look repetitive and long and clumsy. 

The problems and the alternative solutions are further discussed in my blog "Proper Disposal of WCF Channels against a WCF Defect. Follow up". So after generating proxy classes using svcutil.exe, it is better to wrap the proxy client classes with "Safe Channel".

/// <summary>
/// http://webandlife.blogspot.com/2012/08/proper-disposal-of-wcf-channels-against.html
/// </summary>
/// <typeparam name="T"></typeparam>
/// <remarks>Better to put this into another component</remarks>
public class SafeChannel<T> : IDisposable where T : ICommunicationObject, IDisposable
{
    public SafeChannel(T channel)
    {
        Instance = channel;
    }
    public static IDisposable AsDisposable(T client)
    {
        return new SafeChannel<T>(client);
    }
    public T Instance { get; private set; }
    bool disposed;
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    private void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                Close();
            }
            this.disposed = true;
        }
    }
    void Close()
    {
        bool success = false;
        try
        {
            if (Instance.State != CommunicationState.Faulted)
            {
                Instance.Close();
                success = true;
            }
        }
        finally
        {
            if (!success)
                Instance.Abort();
        }
    }
}

public class RealWorldProxy : SafeChannel<Fonlow.RealWorldService.Clients.Service1Client>
{
    public RealWorldProxy(string endpointName)
        : base(new Fonlow.RealWorldService.Clients.Service1Client(endpointName))
    {

    }
}

Integration Tests with Automatic Setup and Teardown of Services

There are quite a few ways to construct integration tests by developers:

  1. Set multiple startup projects in the same VS solution.
  2. Host the services in IIS, and run the integration test suit either in VS IDE or a standalone runner. And you can attach service codes to respective w3wp.exe instance for debugging.
  3. Create service projects in A.Sln, and the test suit in B.Sln, so you can step through both the service hosted in IIS Express launched by VS IDE, and the client codes.

However, sometimes I would like to put the test suits and the service codes in the same VS solution file, and run tests with automatic setup and teardown of the services.

Generally I would write integration test suits on NUnit or xUnit, rather than MS Test, so I could run the test suits during deployment on host machines without Visual Studio installed.

 

public class TestConstants
{
    public const string IisExpressAndInit = "IISExpressStartup";
}

[CollectionDefinition(TestConstants.IisExpressAndInit)]
public class IisCollection : ICollectionFixture<Fonlow.Testing.IisExpressFixture>
{
    // This class has no code, and is never created. Its purpose is simply
    // to be the place to apply [CollectionDefinition] and all the
    // ICollectionFixture<> interfaces.
}
[Collection(TestConstants.IisExpressAndInit)]
public class IntegrationTest
{
    const string realWorldEndpoint = "DefaultBinding_RealWorld";

    [Fact]
    public void TestGetData()
    {
        using (RealWorldProxy client = new RealWorldProxy(realWorldEndpoint))
        {
            client.Instance.ClientCredentials.UserName.UserName = "test";
            client.Instance.ClientCredentials.UserName.Password = "tttttttt";
            Assert.True(client.Instance.GetData(1234).Contains("1234"));
        }

 

The IIsExpressFixture is responsible for starting IIS Express at the beginning and stopping the service after all test cases in all test classes decorated with the same XUnit.CollectionAttribute in assembly TestRealWorldIntegration.dll are finished.

In app.config of TestRealWorldIntegration,

<appSettings>
  <add key="Testing_UseIisExpress" value="True" />
  <add key="Testing_HostSite" value="WcfService1" />
  <add key="Testing_HostSiteApplicationPool" value="Clr4IntegratedAppPool" />
  <add key="Testing_SlnRoot" value="C:\VSProjectsMy\HelloWorldAuth" />
  <add key="Testing_BaseUrl" value="https://localhost:44300/" />
</appSettings>

The appSettings define some keys to be used by IisExpressAndInit for launching IIS Express and direct IIS Express to run WcfService1.

 Hints:

In Visual Studio 2012, 2013 and 2015 before Update 1, IIS Express uses %userprofile%\documents\iisexpress\config\applicationhost.config for all host sites, registered by Visual Studio in the first run of Web applications or services. In Visual Studio 2015 Update 1, IIS Express uses YourVsSlnRootFolder\.vs\config\applicationhost.config. This is why the Sln Root folder has to be defined in the app.config of the test assembly.

 

I would run all tests regularly including unit tests and integration tests.

And you will see that IIS Express is launched by the IIsExpressAndInit .

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, 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. Smile | :)

Points of Interest

Over years I have written a lot WCF services, reviewed/fixed legacy codes left by other developers, and consumed Web services from other vendors. I find that malpractice or inefficient practices resulting in dirty solutions were wide spread, consuming too much of company's wallets and developers' life unnecessarily.

While I firmly believe the essence of the problems are in project management and company culture, particularly the inappropriate mindsets when employing Agile practices or Scrum framework, I consider that developers themselves should be thrilled to write least amount of codes in least complexity, and automate their development works as much as possible.

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. It took me very little efforts to move the database from the default MS SQL to MySql.

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. Programming on the hardware was really fun, feeling like driving the hardware directly.

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

You may also be interested in...

Pro
Pro

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.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160811.3 | Last Updated 14 Feb 2016
Article Copyright 2014 by Zijian
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid