Foundations

Adding Code Access Security to WCF, Part 2

Juval Lowy

Code download available at:Foundations2008_07.exe(497 KB)

Contents

Host-Side CAS in the .NET Framework 3.5
Partially Trusted Services
App Domain Host
Implementing AppDomainHost
Partially Trusted Hosts
Structured Host-Side Security Demands
Implementing Structured Host Demands
Additional Aspects of AppDomainHost

In the April 2008 installment of this column, I discussed the lack of adequate support for code access security (CAS) and the motivation for adding proper CAS support to the Windows® Communication Foundation (WCF). I then proceeded to show how to add that support on the client side, enabling partially trusted clients to call WCF services, all without compromising on security or on the WCF programming model (for the April column, see msdn.microsoft.com/magazine/cc500644).

In this issue, I will show you what it takes to complete the picture by allowing partially trusted services and partially turreted hosts. As with the client side, I will share with you not just the mechanics of the solution but also the approach and the thought process leading to it. You will also get to see some advanced programming techniques in the WCF and Microsoft® .NET Framework.

Host-Side CAS in the .NET Framework 3.5

In the .NET Framework 3.5, WCF only allows partially trusted code to host a service over the BasicHttpBinding, WSHttpBinding, and WebHttpBinding bindings, and only with no security at all or with transport security. Furthermore, in the case of WSHttp­Binding, aspects such as message security, reliable messaging, and transactions are disallowed. All partial-trust enabled bindings must use text encoding. A service running under partial trust cannot use additional facilities such as diagnostics and performance counters.

Enforcing the limited set of supported features is the responsibility of the bindings. Each non-HTTP binding actively demands full trust of its service host. The allowed HTTP bindings themselves do not demand full trust; they instead demand permissions according to the context of use, as listed in Figure 1.

Figure 1 Allowed HTTP Bindings Demands on the Host

Scenario Permissions
Basic, Web, and WS- with no security or transport security Security permission with execution and Infra-structure; Web permission to accept calls to URI.
Above bindings with internal service type in separate assembly Unrestricted reflection.
Above bindings with authenticated calls Security permission to control principal.

The allowed HTTP bindings all demand permission to execute and permission to modify the infrastructure (security permission with infrastructure flag), as well as permission to accept calls on the URI configured for their endpoint (Web permission with accept flag to the URI). When the calls are authenticated, the allowed HTTP bindings also demand permission to control the thread principal (security permission with principal control flag).

This is done because, after authentication, WCF will install a new principal with identity matching the provided client's credentials for the duration of the call and regardless of the binding. If the service type provided to the host constructor is defined as an internal type of another assembly, the host also demands unrestricted reflection permission to be able to load that type using reflection. (The decision to demand infrastructure permission is puzzling since there is no obvious need for it.)

There are additional limitations on configuration that you need to consider. For example, the .config file cannot contain any reference to any certificate store (for service-side certificates), since touching the certificate store will cause WCF to demand full trust. Administrators have to configure these certificates separately using tools such as HttpConfig.exe.

A deficiency of basing the host-side permissions demand solely on the binding is that the hosted service instance is implicitly given these permissions, even if it does not require them to function. For example, the service instance could control the principal or reflect internal types of other assemblies on the host machine. It would have been better to design the bindings to demand their permissions and yet somehow provide the host with a way of granting different permissions to the service, while ensuring that the service permissions are a subset of the hosting code permissions.

Ideally, you would like to tap into the full power of WCF, from distributed transactions to reliable calls, to various security credentials types, to intranet (or even same-machine) applications with TCP and the interprocess communication—IPC (named pipes)—binding, not to mention duplex callbacks, asynchronous calls, diagnostics and tracing, instrumentation, and, of course, queued calls over Microsoft Message Queue (MSMQ). And you would like do all that without compromising on CAS (that is, resorting to full trust).

Partially Trusted Services

In the .NET Framework 3.0, the only way for a service to execute in partial trust was to explicitly permit only the permissions required for it to operate and therefore to implicitly deny all other permissions. One way of achieving that was to apply the matching permission attributes with the SecurityAction.PermitOnly flag. Consider the service in the following:

[SecurityPermission(
  SecurityAction.PermitOnly,
  Execution = true)]
[UIPermission(SecurityAction.PermitOnly,
  Window = 
    UIPermissionWindow.SafeTopLevelWindows)]
class MyService : IMyContract {
  public void MyMethod() {
    Form form = new TestForm();
    form.ShowDialog();
  }
}

The service requires permission in order for it to execute (as does all managed code), and it also must have permission in order for it to display safe windows. If you were to stack multiple permit-only permission attributes on a class, it would yield a single permission set at run time.

The .NET Framework uses it in order to allow only those permissions and to actively deny all other permissions by installing a dedicated stack walk modifier. As a result of this, even if the assembly the service resides in (as well as the app domain) is granting the service full trust, all other permission will be denied.

When the service tries to perform an operation such as opening a file, that will trigger a security exception, since the demand for file I/O will encounter the stack walk modifier that will actively deny having that permission. All the service can do is execute in its virtual sandbox and display safe windows. The .NET Framework will change the window caption and display a warning tag on the displayed form to inform the user that the application is partially trusted, as seen in Figure 2.

foundfig03.gif

Figure 2 Window Displayed from Partially Trusted Code

Instead of specifying the permissions as attributes, you can list them in a permission set XML file and provide that file name to the PermissionSetAttribute, as shown in Figure 3.

Figure 3 Using Permission Set File for Partially Trusted Service

<!-- MyServicePermissions.xml --> 
<PermissionSet class = "System.Security.PermissionSet">
  <IPermission 
    class = "System.Security.Permissions.SecurityPermission" 
    Flags = "Execution"
  />
  <IPermission 
    class = "System.Security.Permissions.UIPermission" 
    Window = "SafeTopLevelWindows"
  />
</PermissionSet>

[PermissionSet(SecurityAction.PermitOnly,File = 
  "MyServicePermissions.xml")]
class MyService : IMyContract {
  public void MyMethod() {
    Form form = new TestForm();
    form.ShowDialog();
  }
}

Note that the permission set file is only used at compile time, and it has no use for deployment. The compiler will fuse the permitted permissions into the class metadata as individual attributes. If the file is not present, the build will fail.

App Domain Host

There are several problems with the attribute-based approach to partially trusted services. First, every time there is a change in what the service does—or for that matter any of the downstream classes it uses—you will need to modify the stack of attributes or change the file. This will couple the service to those classes and increase the maintenance cost.

Second, because the permissions are part of the service definition, there is no way to use the service with different permissions as a product of the hosting environment. You cannot give it more or fewer permissions.

Third, and more important, it is unlikely most services will actually go through the trouble of analyzing the security permissions required for them to operate and meticulously reduce their granted permissions in order to run with the least-possible privileges and permissions. If anything, it is the host that should be concerned about what exactly the service it loads is up to, and yet the host by default has no ability to affect that. The ServiceHost of WCF will host all services in full trust.

It would be better for the host, as well as for the service's own protection, to allow the host to grant whichever permissions it deems as appropriate for the service. To that end, I wrote the class AppDomainHost defined in Figure 4.

Figure 4 AppDomainHost

public class AppDomainHost : IDisposable {
  //Create new app domain in full trust
  public AppDomainHost(Type serviceType,params Uri[] baseAddresses);
  public AppDomainHost(Type serviceType,string appDomainName,
    params Uri[] baseAddresses);

  //Create new app domain with specified permission set
  public AppDomainHost(Type serviceType,PermissionSet permissions,
    params Uri[] baseAddresses);
  public AppDomainHost(Type serviceType,PermissionSet permissions,
    string appDomainName,params Uri[] baseAddresses);

  //Additional constructors that take standard permission set, 
  //permission set filename, and an existing app domain  

  public void Open();
  public void Close();
  public void Abort();

  //More members 
}

The class AppDomainHost can be used just like the WCF-provided ServiceHost:

AppDomainHost host = 
  new AppDomainHost(typeof(MyService));
host.Open();

The difference is that AppDomainHost will host the provided service type in a new app domain, not in the app domain of its caller. The new app domain name will default to "AppDomain Host for" suffixed with the service type and a new GUID. You can also specify a name for the new app domain:

AppDomainHost host = 
  new AppDomainHost(typeof(MyService),"My App Domain");
host.Open();

The new app domain is created with full trust by default. However, placing the service in a separate app domain is the key for managing partially trusted services.

AppDomainHost allows you to provide the permissions for the new app domain. For example, Figure 5 shows a service and a host granting the service just enough permissions required in order for it to operate.

Figure 5 Partially Trusted Service

class MyService : IMyContract {
  public void MyMethod() {
    Form form = new TestForm();
    form.ShowDialog();
  }
}

//Hosting code:
PermissionSet permissions = 
  new PermissionSet(PermissionState.None);
permissions.AddPermission(new SecurityPermission(
  SecurityPermissionFlag.Execution));
permissions.AddPermission(new UIPermission(
  UIPermissionWindow.SafeTopLevelWindows));

AppDomainHost host = 
  new AppDomainHost(typeof(MyService),permissions);

host.Open();

You can also use a permission set file, and, unlike that which was shown in Figure 3, the file is only needed at run time and can be modified post deployment. It is important to note that you also have the ability to specify one of the standard named permission sets. The virtual sandbox imposed on partially trusted services affects any downstream class it may use along with its ability to call outside the sandbox over any technology, from the .NET Framework 1.0 to WCF.

A single host process can have many instances of AppDomainHost, each hosting the same or a different service type, each with any arbitrary set of permissions, as shown in Figure 6 (using the same service definition as in Figure 5).

Figure 6 Same Service Type with Different Permissions

//Default is service with full trust
AppDomainHost host0 = 
  new AppDomainHost(typeof(MyService),
  "Full Trust App Domain",
  new Uri("net.tcp://localhost:6000"));

host0.Open();

//With just enough permissions to do work
PermissionSet permissions1 = 
  new PermissionSet(PermissionState.None);
permissions1.AddPermission(new SecurityPermission(
  SecurityPermissionFlag.Execution));
permissions1.AddPermission(new UIPermission(
  UIPermissionWindow.SafeTopLevelWindows));

AppDomainHost host1 = 
  new AppDomainHost(typeof(MyService),permissions1,
  "Partial Trust App Domain",
  new Uri("net.tcp://localhost:6001"));

host1.Open();

//With not enough permissions to do work
PermissionSet permissions2 = 
  new PermissionSet(PermissionState.None);
permissions2.AddPermission(new SecurityPermission(
  SecurityPermissionFlag.Execution));

AppDomainHost host2 = 
  new AppDomainHost(typeof(MyService),permissions2,
  "Not enough permissions",new Uri("net.tcp://localhost:6002"));

host2.Open();

//Using one of the named permission sets
AppDomainHost host3 = 
  new AppDomainHost(typeof(MyService),
  StandardPermissionSet.Internet,
  "Named permission set",
  new Uri("net.tcp://localhost:6003"));

host3.Open();

Implementing AppDomainHost

Implementing AppDomainHost is done in two parts: creating a new app domain and injecting a service host instance, and then having the service host (and the service instance) execute in partial trust. To create a service host instance and to activate it in a separate app domain, I wrote the class ServiceHostActivator shown in Figure 7.

Figure 7 ServiceHostActivator

class ServiceHostActivator : MarshalByRefObject {
  ServiceHost m_Host;

  public void CreateHost(Type serviceType,Uri[] baseAddresses) {
    m_Host = new ServiceHost(serviceType,baseAddresses);
  }
  public void Open() {
    m_Host.Open();
  }   
  public void Close() {
    m_Host.Close();
  }
  public void Abort() {
    m_Host.Abort();
  }

  //Rest of the implementation
}

ServiceHostActivator is a simple wrapper around a standard WCF-provided ServiceHost instance. ServiceHostActivator derives from MarshalByRefObject so that AppDomainHost could call it across the app domain boundary. The CreateHost method encapsulates constructing a new Service­Host instance. The rest of the methods of Service­HostActivator simply forward the remote calls to the underlying host instance.

AppDomainHost offers several overloaded constructors. These constructors all call each other (see Figure 8), even creating a new app domain along the way and eventually the construction ends up using a protected constructor that takes the service type, the new app domain instance, the permission set for the new domain, and the base addresses.

Figure 8 Implementing AppDomainHost

public class AppDomainHost : IDisposable {
  ServiceHostActivator m_ServiceHostActivator;

  public AppDomainHost(Type serviceType,
    params Uri[] baseAddresses) :  
    this(serviceType,"AppDomain Host for "+
    serviceType+" "+Guid.NewGuid(),
    baseAddresses) {
  }

  public AppDomainHost(Type serviceType,
    string appDomainName,
    params Uri[] baseAddresses) : this(serviceType,
    new PermissionSet(PermissionState.Unrestricted),
    appDomainName,baseAddresses) {
  }

  public AppDomainHost(Type serviceType,
  PermissionSet permissions,
  string appDomainName,
  params Uri[] baseAddresses) : 
  this(serviceType,AppDomain.CreateDomain(appDomainName),
  permissions,baseAddresses) {
  }

  //More constructors 

  protected AppDomainHost(Type serviceType,
    AppDomain appDomain,
    PermissionSet permissions,Uri[] baseAddresses) {

    string assemblyName = Assembly.GetAssembly(
      typeof(ServiceHostActivator)).FullName;
    m_ServiceHostActivator = appDomain.CreateInstanceAndUnwrap(
      assemblyName,typeof(ServiceHostActivator).ToString()) as 
      ServiceHostActivator;

    CodeAccessSecurityHelper.SetPermissionsSet(appDomain,permissions);

    m_ServiceHostActivator.CreateHost(serviceType,baseAddresses);
  }

  public void Open() {
    m_ServiceHostActivator.Open();
  }
  public void Close() {
    m_ServiceHostActivator.Close();
  }
  public void Abort() {
    m_ServiceHostActivator.Abort();
  }
  void IDisposable.Dispose() {
    Close();
  }
}

The protected constructor of AppDomainHost uses .NET remoting to inject into the new app domain an instance of ServiceHostActivator, ending with a remoting proxy to it, stored in the m_ServiceHostActivator member.

The new app domain is created with full trust by default. App­DomainHost uses the SetPermissionsSet method of my CodeAccess­SecurityHelper class in order to install a new CAS policy in the new app domain—permitting only the supplied permissions and denying the rest:

public static class CodeAccessSecurityHelper {
  public static void SetPermissionsSet(
    AppDomain appDomain,
    PermissionSet permissions) {

    PolicyLevel policy = PolicyLevel.CreateAppDomainLevel();
    policy.RootCodeGroup.PolicyStatement = 
      new PolicyStatement(permissions);
    appDomain.SetAppDomainPolicy(policy);
  }
  //More members
}

It is as simple as creating a new security policy at the app-domain level and calling the SetAppDomainPolicy method of the AppDomain class:

public sealed class AppDomain : 
  MarshalByRefObject,... {

  public void SetAppDomainPolicy(
    PolicyLevel domainPolicy);
  //More members
}

Incidentally, a similar technique is used by ClickOnce to enforce the partial-trust execution of the ClickOnce-deployed applications.

Calling the other methods of AppDomainHost such as Open or Close uses the proxy to ServiceHostActivator to call to the other app domain and have it open or close its host instance. Since the service instance will execute in the app domain that happened to open its host, the service will also execute under that app domain's security policy.

Partially Trusted Hosts

The approach presented thus far for partially trusted services was predicated on having the code that uses AppDomainHost running under full trust. This is because ServiceHost and the bindings will demand full trust in any hosting scenario that is not listed in Figure 1.

But what if the code launching the host is only partially trusted? You could place AppDomainHost and ServiceHostActivator in a fully trusted assembly, allow partially trusted callers and have them both assert full trust:

[PermissionSet(SecurityAction.Assert,Unrestricted = true)]
public class AppDomainHost : IDisposable
{...}

[PermissionSet(SecurityAction.Assert,Unrestricted = true)]
class ServiceHostActivator : MarshalByRefObject
{...}

While this approach works, it circumvents CAS and disables a crucial security mechanism, and by doing so you introduce two security problems. First, it is incorrect to assume that the code opening the host has permissions to accept calls on the transport channels or participate in any of the WCF activities such as distributed transactions. Second, by asserting full trust and suppressing the stack walk, the partially trusted code that launched the host can actually be used to create a service with higher privileges to do its dirty deeds for it. For example, the hosting code may not have file I/O permissions, but it will use AppDomainHost to accept calls to a service with file I/O permissions.

The solution is simple: AppDomainHost and ServiceHost­Activator should not employ a blank assertion of full trust. Instead, they should only assert locally, when needed, and then revert to the existing permissions. In addition, AppDomainHost should challenge the code using it and demand appropriate hosting permissions (such as accepting calls). It should also verify that the hosting code is granted at least the permissions it wishes to run the service under. This will prevent a restricted and partially trusted code from using a service hosted in a separate app domain with more permission. The net result is structured host-side security demands.

Structured Host-Side Security Demands

The reworked ServiceHostActivator, suitable for partial trust callers, is shown in Figure 9. The CreateHost method of ServiceHost­Activator cannot declaratively assert full trust, since this will prevent it from demanding the appropriate host permission of the calling code. Instead, it programmatically uses CodeAccessSecurity­Helper to create a full-trust permission set and assert it, then proceeds to create the host. When WCF demands full trust, the assertion prevents that demand from going up the call stack.

Figure 9 Reworked ServiceHostActivator

class ServiceHostActivator : MarshalByRefObject {
  public void CreateHost(Type serviceType,Uri[] baseAddresses) {
    CodeAccessSecurityHelper.PermissionSetFromStandardSet(
    StandardPermissionSet.FullTrust).Assert();

    m_Host = new ServiceHost(serviceType,baseAddresses);

    PermissionSet.RevertAssert();

    m_Host.DemandHostPermissions();
  }

  //Behavior demands happen here, must assert 
  [PermissionSet(SecurityAction.Assert,Unrestricted = true)]
  public void Open() {
    m_Host.Open();
  }   
  ...
}

After creating the host, CreateHost explicitly reverts the assertion. CreateHost then calls the extension helper method DemandHostPermissions of CodeAccessSecurityHelper on the just-created host instance in order to demand the hosting permissions. DemandHostPermissions examines the host object and demands the appropriate permissions.

For example, if the host supports a TCP endpoint, then DemandHostPermissions will demand permission to execute and permission to accept TCP calls on the endpoint URI. If reliable messaging is used over TCP, DemandHostPermissions will demand permission to control the policy. There are quite a few additional possible demands, as shown in Figure 10.

Figure 10 Structured Host-Side Demands with AppDomainHost

Scenario Permissions
TCP Security permission with execution, Socket permission to accept TCP call on URI.
IPC Security permission with execution, control policy, and control evidence.
MSMQ Security permission with execution, MSMQ permission to read from queue.
WS, WS-Dual, Basic, Web Security permission with execution and Web permission to accept calls on URI.
RM over TCP Security permission to control policy.
Authenticated calls Security permission to control principal.
Transaction propagation Unrestricted distributed transaction permission.
User name credentials with message security, certificate credentials with validation, and service certificate Store permission to enumerate stores, open store, and enumerate certificates.
Diagnostic tracing Environment permission to read COMPUTERNAME, File I/O permission to path discovery, append, and write to log files.
WCF service performance counters Performance counter permission to write to service counter, write to endpoint counter, and write to operation counter.
WCF all performance counters Performance counter permission to write to service counter, write to endpoint counter, write to operation counter, and write to host counter.
ASP.NET providers Minimal ASP.NET hosting permission.

Use of the IPC binding demands permission to control policy and evidence. Use of the MSMQ binding triggers a demand for permission to read from the endpoint queue. Use of any of the HTTP bindings triggers demand to accept calls on the endpoint address. If the binding uses authenticated calls, DemandHostPermissions will demand permissions to control the principal.

Use of a transaction-aware binding, with transaction flow enabled and having at least one operation on the endpoint's contract with a transaction flow allowed (or mandatory), will cause a demand for unrestricted distributed transaction permission.

Any access to the certificate store, such as with message security, client certificate validation, or use of a service certificate to protect the message, will cause demand for store enumeration to open a store and enumerate the certificates.

If the serviced performs diagnostics, DemandHostPermissions will demand access to the computer name environment variable and file I/O permission to that file. If the host uses WCF performance counters, DemandHostPermissions will demand permissions to write to the counters at the reported level.

If the host relies on the ASP.NET providers for caller authentication or role membership, DemandHostPermissions will demand minimal ASP.NET hosting permission (all providers require that permission). When calling back over the WS-Dual binding, the binding itself will demand Web permission to connect to that callback endpoint, so there is no need for AppDomainHost to explicitly demand it when launching the host.

Implementing Structured Host Demands

Figure 11 shows the partial implementation of DemandHostPermissions, which first performs endpoint-specific demands. It iterates over the collection of the service's endpoints, and for each endpoint it demands its respective connectivity, security, and transactions permissions.

Figure 11 Implementing DemandHostPermissions

public static class CodeAccessSecurityHelper {
  internal static void DemandHostPermissions(this ServiceHost host) {
    foreach(ServiceEndpoint endpoint in host.Description.Endpoints) {
      DemandHostConnectionPermissions(endpoint);
      DemandHostSecurityPermissions(endpoint);
      using(TransactionScope scope = new TransactionScope()) {
        DemandTransactionPermissions(endpoint);
      }
    }
    DemandHostStorePermissions(host);
    DemandPerformanceCounterPermissions();
    DemandTracingPermissions();
    DemanAspNetProvidersPermissions(host);
  }
  internal static void DemandHostConnectionPermissions(
    ServiceEndpoint endpoint) {

    PermissionSet connectionSet = 
      new PermissionSet(PermissionState.None);
    if(endpoint.Binding is NetTcpBinding) {
      connectionSet.AddPermission(new SocketPermission(
        NetworkAccess.Accept, TransportType.Tcp,
        endpoint.Address.Uri.Host,endpoint.Address.Uri.Port));
    }

    /* Checking the other bindings */

    connectionSet.Demand();
  }

  static void DemanAspNetProvidersPermissions(ServiceHost host) {
    bool demand = false;

    foreach(IServiceBehavior behavior in host.Description.Behaviors) {
      if(behavior is ServiceCredentials) {
        ServiceCredentials credentialsBehavior = 
          behavior as ServiceCredentials;

        if(credentialsBehavior.UserNameAuthentication.
          UserNamePasswordValidationMode == 
          UserNamePasswordValidationMode.MembershipProvider)  {

          demand = true;
          break;
        }
      }
      if(behavior is ServiceAuthorizationBehavior) {
        ServiceAuthorizationBehavior serviceAuthorization = 
          behavior as ServiceAuthorizationBehavior;
        if(serviceAuthorization.PrincipalPermissionMode == 
          PrincipalPermissionMode.UseAspNetRoles && 
          Roles.Enabled) {

          demand = true;
          break;
        }
      }
    }
    if(demand) {
      IPermission permission = 
        new AspNetHostingPermission(
        AspNetHostingPermissionLevel.Minimal);
      permission.Demand();
    }
  }
  //Rest of the implementation 
}

For the transactions demand, it creates a temporary ambient transaction using a transaction scope. This enables it to use the same method as on the client side, which demands permissions at call time based on the presence of an ambient transaction.

After the endpoints demands, DemandHostPermissions demands permissions that are a product of the service host configuration, specifically certificates store access permission, performance counters permissions, tracing permissions, and ASP.NET providers permissions. For example, to demand the ASP.NET providers permissions, it obtains the collection of service behaviors and sees whether the host's service credentials behavior uses the membership provider or the host's service authorization behavior uses the roles providers, and if so it demands the ASP.NET hosting permission.

AppDomainHost too needs some rework to support structured demands in a partial-trust environment, as shown here:

[SecurityPermission(
  SecurityAction.Assert,ControlAppDomain = true)]
[ReflectionPermission(
  SecurityAction.Assert,Unrestricted = true)]
public class AppDomainHost : IDisposable {
  protected AppDomainHost(Type serviceType,
    AppDomain appDomain,
    PermissionSet permissions,
    params Uri[] baseAddresses) {
    //Cannot grant service permissions 
    // the host does not have
    permissions.Demand();

    //Rest of constructor 
    }
}

Since AppDomainHost creates a new app domain, it asserts the permission to control the app domain. Since it uses reflection to inject the host in the other app domain, it asserts unrestricted reflection permission. Most importantly, before creating the service host in the new app domain, AppDomainHost demands the requested permissions for the service of the code that calls it. This ensures that partially trusted code can only launch and host a service that is permitted to do as much as, and no more than, the calling code.

Additional Aspects of AppDomainHost

While AppDomainHost does not support ICommunication­Object, it does offer its events model and state machine management. AppDomainHost also copies a few static variables from the calling app domain to the new app domain it created. Specifically, it uses ServiceHostActivator to copy to the other app domain the ASP.NET providers application name. AppDomainHost temporarily asserts the ASP.NET hosting permission, accesses the providers, copies the values of the membership and roles application names, and reverts the assertion.

Juval Lowy is a software architect with IDesign providing WCF training and architecture consulting. His recent book is Programming WCF Services. He is also the Microsoft Regional Director for the Silicon Valley. Contact Juval at www.idesign.net.