Section 10.8. Internet Application


10.8. Internet Application

In the Internet scenario, the clients or services may not use WCF or even Windows. If you are writing a service or a client, you cannot assume the use of WCF on the other end. In addition, an Internet application typically has a relatively large number of clients calling the service. These client calls originate from outside the firewall. You need to rely on HTTP for transport, and multiple intermediaries are possible. In an Internet application, you typically do not want to use Windows accounts and groups for credentials and instead the application needs to access some custom credentials store. That said, you could still be using Windows security, as demonstrated later on.

10.8.1. Securing the Internet Bindings

In the Internet application, you must use message security for transfer security to provide for end-to-end security across all intermediaries. The client should provide credentials in the form of username and password. For the Internet scenario you should use the WSHttpBinding and WSDualHttpBinding. In addition, if you have an intranet application that uses the NetTcpBinding but you do not wish to use Windows security for user accounts and groups, you should follow the same configuration as with the WS-based bindings. This is done uniformly across these bindings by selecting MessageCredentialType.Username for the client credentials type used with the message security. You need to configure the bindings this way both at the client and at the service.

10.8.1.1. WSHttpBinding configuration

WSHttpBinding offers the Security property of the type WSHttpSecurity:

 public class WSHttpBinding : WSHttpBindingBase {    public WSHttpBinding( );    public WSHttpBinding(SecurityMode securityMode);    public WSHttpSecurity Security    {get;}    //More members } 

With WSHttpSecurity, you need to set the Mode property of the type SecurityMode to SecurityMode.Message. The Message property of WSHttpSecurity will then take effect:

 public sealed class WSHttpSecurity {    public SecurityMode Mode    {get;set;}    public NonDualMessageSecurityOverHttp Message    {get;}    public HttpTransportSecurity Transport    {get;} } 

Message is of the type NonDualMessageSecurityOverHttp, which derives from MessageSecurityOverHttp:

 public class MessageSecurityOverHttp {    public MessageCredentialType ClientCredentialType    {get;set;}    //More members } public sealed class NonDualMessageSecurityOverHttp :                                                MessageSecurityOverHttp {...} 

You need to set the ClientCredentialType property of MessageSecurityOverHttp to MessageCredentialType.Username. Recall that the default Message credentials type of WSHttpBinding is Windows (see Table 10-3).

Because Message security is the default security mode of WSHttpBinding (see Table 10-1), these three definitions are equivalent:

 WSHttpBinding binding1 = new WSHttpBinding( ); binding1.Security.Message.ClientCredentialType = MessageCredentialType.UserName; WSHttpBinding binding2 = new WSHttpBinding(SecurityMode.Message); binding2.Security.Message.ClientCredentialType = MessageCredentialType.UserName; WSHttpBinding binding3 = new WSHttpBinding( ); binding3.Security.Mode = SecurityMode.Message; binding3.Security.Message.ClientCredentialType = MessageCredentialType.UserName; 

Or when using a config file:

 <bindings>    <wsHttpBinding>       <binding name = "UserNameWS">          <security mode = "Message">             <message clientCredentialType = "UserName"/>          </security>       </binding>    </wsHttpBinding> </bindings> 

And since Message security is the default, you can omit explicitly setting the mode in the config file:

 <bindings>    <wsHttpBinding>       <binding name = "UserNameWS">          <security>             <message clientCredentialType = "UserName"/>          </security>       </binding>    </wsHttpBinding> </bindings> 

Figure 10-4 shows the security-related elements of the WSHttpBinding.

Figure 10-4. WSHttpBinding and security


WSHttpBinding has a reference to WSHttpSecurity, which uses the SecurityMode enum to indicate the transfer security mode. When transport security is used, WSHttpSecurity will use an instance of HttpTransportSecurity. When Message security is used, WSHttpSecurity will use an instance of NonDualMessageSecurityOverHttp containing the client credential type via the MessageCredentialType enum.

10.8.1.2. WSDualHttpBinding configuration

WSDualHttpBinding offers the Security property of the type WSDualHttpSecurity:

 public class WSDualHttpBinding : Binding,... {    public WSDualHttpBinding( );    public WSDualHttpBinding(WSDualHttpSecurityMode securityMode);    public WSDualHttpSecurity Security    {get;}    //More members } 

With WSDualHttpSecurity, you need to set the Mode property of the type WSDualHttpSecurityMode to WSDualHttpSecurityMode.Message. The Message property of WSDualHttpSecurity will then take effect:

 public sealed class WSDualHttpSecurity {    public MessageSecurityOverHttp Message    {get;}    public WSDualHttpSecurityMode Mode    {set;set;} } 

Message is of the type MessageSecurityOverHttp presented earlier.

You need to set the ClientCredentialType property of MessageSecurityOverHttp to MessageCredentialType.Username. Recall that the default Message credentials type of WSDualHttpBinding is Windows (see Table 10-3).

Because Message security is the default transfer security mode of WSDualHttpBinding (see Table 10-1), these definitions are equivalent:

 WSDualHttpBinding binding1 = new WSDualHttpBinding( ); binding1.Security.Message.ClientCredentialType = MessageCredentialType.UserName; WSDualHttpBinding binding2 = new WSDualHttpBinding(WSDualHttpSecurityMode.Message); binding2.Security.Message.ClientCredentialType = MessageCredentialType.UserName; WSDualHttpBinding binding3 = new WSDualHttpBinding( ); binding3.Security.Mode = WSDualHttpSecurityMode.Message; binding3.Security.Message.ClientCredentialType = MessageCredentialType.UserName; 

Or when using a config file:

 <bindings>    <wsDualHttpBinding>       <binding name = "WSDualWindowsSecurity">          <security mode = "Message">             <message  clientCredentialType = "UserName"/>          </security>       </binding>    </wsDualHttpBinding> </bindings> 

And since Message security is the default, you can omit explicitly setting the mode in the config file:

 <bindings>    <wsDualHttpBinding>       <binding name = "WSDualWindowsSecurity">          <security>             <message  clientCredentialType = "UserName"/>          </security>       </binding>    </wsDualHttpBinding> </bindings> 

Figure 10-5 shows the security-related elements of WSDualHttpBinding.

Figure 10-5. WSDualHttpBinding and security


WSDualHttpBinding has a reference to WSDualHttpSecurity, which uses the WSDualHttpSecurityMode enum to indicate the transfer security mode: Message or None. When Message security is used, WSDualHttpSecurity will use an instance of MessageSecurityOverHttp containing the client credential type via the MessageCredentialType enum.

10.8.2. Message Protection

Since in the Internet scenario the client's message sent to the service is transferred over plain HTTP, it is vital to protect its content (both the client's credentials and the body of the message) by encrypting it. Encryption will provide for message integrity and privacy. One technical option for encryption is to use the client's password. However, WCF never uses the client's password for encrypting the message, for a number of reasons. First, there are no guarantees that the password is strong enough, so that anyone monitoring the communication could not break the encryption. Second, it will force the service (more precisely, its host) to have access to the password, and thus couple the host to the credential store. Finally, while the password may protect the message, it will not provide for service authenticity toward the client.

To protect the message, WCF uses an X509 certificate. The certificate provides strong protection, and it uniquely authenticates the service toward the client. The way a certificate works is by using two keys, called the public and private keys, as well as a common name (CN) such as MyServiceCert. What is important about those keys is that anything encrypted with the public key can only be decrypted with the private one. The certificate contains the public key and the common name, and the private key is kept in some secure storage on the host machine that the host has access to. The host makes the certificate (and its public key) publicly available; that is, any client can access the host's endpoints and obtain the public key.

In a nutshell, what happens during a call is that WCF on the client's side will use the public key to encrypt all messages to the service. Upon receiving the encrypted message, WCF on the host side will decrypt the message using the private key. Once the message is decrypted, WCF will read the client's credentials from the message, authenticate the client, and allow it to access the service. The real picture is a bit more complex, because WCF also needs to secure the reply messages and callbacks from the service to the client. One of the standards WCF supports deals with setting up such a secure conversation. There are, in fact, several calls done before the first request message from the client to the service, where WCF on the client's side generates a shared secret it passes encrypted (using the service certificate) to the service. The service will then use that to encrypt the reply messages as well as the callbacks if any.

10.8.2.1. Configuring host certificate

The ServiceHostBase class offers the Credentials property of the type ServiceCredentials. ServiceCredentials is a service behavior:

 public abstract class ServiceHostBase : ... {    public ServiceCredentials Credentials    {get;}    //More members } public class ServiceCredentials : ...,IServiceBehavior {    public X509CertificateRecipientServiceCredential ServiceCertificate    {get;}    //More members } 

ServiceCredentials provides the ServiceCertificate property of the type X509CertificateRecipientServiceCredential:

 public sealed class X509CertificateRecipientServiceCredential {    public void SetCertificate(StoreLocation storeLocation,                               StoreName storeName,                               X509FindType findType,                               object findValue);    //More members } 

You can use the SetCertificate( ) method to instruct WCF where and how to load the service certificate. You typically provide this information in the host config file as a custom behavior under the serviceCredentials section, as shown in Example 10-8.

Example 10-8. Configuring the service certificate

 <services>    <service name = "MyService" behaviorConfiguration = "Internet">       ...    </service> </services> <behaviors>    <serviceBehaviors>       <behavior name = "Internet">          <serviceCredentials>             <serviceCertificate                findValue     = "MyServiceCert"                storeLocation = "LocalMachine"                storeName     = "My"                x509FindType  = "FindBySubjectName"             />          </serviceCredentials>       </behavior>    </serviceBehaviors> </behaviors> 

10.8.2.2. Using the host certificate

The client developer can obtain the service certificate using any out-of-band mechanism (such as an email or via a public web page). The client can then include in its config file in the endpoint behavior section detailed information about the service certificate, such as where it is stored on the client side and how to find it. This is by far the most secure option from the client's perspective, because any attempt to subvert the client's address resolving and redirect the call to a malicious service will fail since the other service will not have the correct certificate. This is the least flexible option as well, however, because every time the client needs to interact with a different service, the client administrator will need to rework the client's config file.

A reasonable alternative to explicitly referencing the certificates of all services the client may interact with is to store those certificates in the client's Trusted People certificate folder. The administrator can then instruct WCF to allow calls only to a service whose certificate is in that folder. The client in that case will need to obtain the service certificate at runtime as part of the initial pre-call negotiation, check to see if it is in the Trusted People store and if so, proceed to use it to protect the message. In fact, certificate negotiation this way is the default behavior of WCF, but you can disable it (and use a hard-configured certificate) with both WSHttpBinding and WSDualHttpBinding. For the Internet scenario, I recommend the use of certificate negotiation coupled with storing the certificates in the Trusted People store.

10.8.2.3. Service certificate validation

To instruct WCF as to what degree to validate and trust the service certificate, add a custom endpoint behavior to the client's config file. The behavior should use the ClientCredentials section. ClientCredentials is an endpoint behavior that offers the ServiceCertificate property of the type X509CertificateRecipientClientCredential:

 public class ClientCredentials : ...,IEndpointBehavior {    public X509CertificateRecipientClientCredential ServiceCertificate    {get;}    //More members } 

X509CertificateRecipientClientCredential offers the Authentication property of the type X509CertificateRecipientClientCredential:

 public sealed class X509CertificateRecipientClientCredential {    public X509ServiceCertificateAuthentication Authentication    {get;}    //More members } 

X509CertificateRecipientClientCredential provides the CertificateValidationMode property of the enum type X509CertificateValidationMode:

 public enum X509CertificateValidationMode {    None,    PeerTrust,    ChainTrust,    PeerOrChainTrust,    Custom } public class X509ServiceCertificateAuthentication {    public X509CertificateValidationMode CertificateValidationMode    {get;set;}    //More members } 

Example 10-9 demonstrates setting the service certificate validation mode in the client's config file.

Example 10-9. Validating the service certificate

 <client>    <endpoint behaviorConfiguration = "ServiceCertificate"       ...    </endpoint> </client> <behaviors>    <endpointBehaviors>       <behavior name = "ServiceCertificate">          <clientCredentials>             <serviceCertificate>                <authentication certificateValidationMode = "PeerTrust"/>             </serviceCertificate>          </clientCredentials>       </behavior>    </endpointBehaviors> </behaviors> 

X509CertificateValidationMode.PeerTrust instructs WCF to trust the negotiated service certificate if it is also present in the Trusted People store of the client. X509CertificateValidationMode.ChainTrust instructs WCF to trust the certificate if it was issued by a trusted root authority (such as VeriSign or Thwart) whose certificate is found in the Trusted Root Authority folder. X509CertificateValidationMode.ChainTrust is the default value used by WCF. X509CertificateValidationMode.PeerOrChainTrust allows either of these options.

10.8.2.4. Working with a test certificate

Developers often do not have access to their organization's certificate, and therefore resort to using test certificates such as the ones generated by the MakeCert.exe command-line utility. There are two problems with test certificates. The first is that they will fail the default certificate validation on the client side since the client uses X509CertificateValidationMode.ChainTrust by default. That can easily be overcome by installing the test certificate in the client's Trusted People store and using X509CertificateValidationMode.PeerTrust or X509CertificateValidationMode.PeerOrChainTrust. The second problem is that WCF by default expects the service certificate name to match the service host's domain (or machine name). To compensate, the client must explicitly specify the test certificate name in the endpoint identity's DNS section:

 <client>    <endpoint       address  = "http://localhost:8001/MyService"       binding  = "wsHttpBinding"       contract = "IMyContract">       <identity>          <dns value = "MyServiceCert"/>       </identity>    </endpoint> </client> 

10.8.3. Authentication

The client needs to provide its credentials to the proxy. The ClientCredentials property (presented earlier) of the ClientBase<T> base class has the UserName property of the type UserNamePasswordClientCredential:

 public class ClientCredentials : ...,IEndpointBehavior {    public UserNamePasswordClientCredential UserName    {get;}    //More members } public sealed class UserNamePasswordClientCredential {    public string UserName    {get;set;}    public string Password    {get;set;} } 

The client uses UserNamePasswordClientCredential to pass to the service its username and password as demonstrated in Example 10-10.

The client need not provide a domain name (if Widows security is used) or application name (if ASP.NET providers are used). The host will use its service domain or a configured application name as appropriate.


Example 10-10. Providing username and passwords credentials

 MyContractClient proxy = new MyContractClient( ); proxy.ClientCredentials.UserName.UserName = "MyUsername"; proxy.ClientCredentials.UserName.Password = "MyPassword"; proxy.MyMethod( ); proxy.Close( ); 

Unlike the intranet scenario (see Example 10-3) where the client has to instantiate a new NetworkCredential object, with the Internet scenario there is no need (or way) to assign a new UserNamePasswordClientCredential object.

When working with a channel factory instead of a proxy class, set the Credentials property of the factory with the credentials:

 ChannelFactory<IMyContract> factory = new ChannelFactory<IMyContract>(""); factory.Credentials.UserName.UserName = "MyUsername"; factory.Credentials.UserName.Password = "MyPassword"; IMyContract proxy = factory.CreateChannel( ); using(proxy as IDisposable) {    proxy.MyMethod( ); } 

Note that you cannot use the static CreateChannel( ) methods of ChannelFactory<T> since you have to instantiate a factory in order to access the Credentials property.

Once the username and password credentials are received by the WCF on the service side, the host can choose to authenticate them as either Windows credentials, ASP.NET membership provider's credentials, or even custom credentials. Whichever option you choose, make sure it matches your role-based policy configuration.

The ServiceCredentials class (the Credentials property of ServiceHostBase) provides the UserNameAuthentication property of the type UserNamePasswordServiceCredential:

 public class ServiceCredentials : ...,IServiceBehavior {    public UserNamePasswordServiceCredential UserNameAuthentication    {get;}    //More members } 

UserNamePasswordServiceCredential has the UserNamePasswordValidationMode property of a matching enum type:

 public enum UserNamePasswordValidationMode {    Windows,    MembershipProvider,    Custom } public sealed class UserNamePasswordServiceCredential {    public MembershipProvider MembershipProvider    {get;set;}    public UserNamePasswordValidationMode UserNamePasswordValidationMode    {get; set;}    //More members } 

By setting the UserNamePasswordValidationMode property, the host chooses how to authenticate the incoming username and password credentials.

10.8.4. Using Windows Credentials

While not necessarily common, WCF lets the Internet-facing service authenticate the incoming credentials as Windows credentials. To authenticate the client's username and password as Windows credentials, you need to set UserNamePasswordValidationMode to UserNamePasswordValidationMode.Windows. Because UserNamePasswordValidationMode.Windows is the default value of the UserNamePasswordValidationMode property, these two definitions are equivalent:

 ServiceHost host1 = new ServiceHost(typeof(MyService)); ServiceHost host2 = new ServiceHost(typeof(MyService)); host2.Credentials.UserNameAuthentication.UserNamePasswordValidationMode =                                             UserNamePasswordValidationMode.Windows; 

When using a config file, add a custom behavior that assigns the username and password authentication mode along with the service certificate information, as shown in Example 10-11.

Example 10-11. Internet security with Windows credentials

 <services>    <service name = "MyService" behaviorConfiguration = "UsernameWindows">       ...    </service> </services> <behaviors>    <serviceBehaviors>       <behavior name = "UsernameWindows">          <serviceCredentials>             <userNameAuthentication userNamePasswordValidationMode = "Windows"/>             <serviceCertificate                ...             />          </serviceCredentials>       </behavior>    </serviceBehaviors> </behaviors> 

As with the programmatic case, adding this line to the config file:

 <userNameAuthentication userNamePasswordValidationMode = "Windows"/> 

is optional because it is the default setting.

10.8.4.1. Authorization

If the PrincipalPermissionMode property of ServiceAuthorizationBehavior is set to its default value of PrincipalPermissionMode.UseWindowsGroups, once the username and password are authenticated against Windows, WCF installs a Windows principal object and attaches it to the thread. This enables the service to freely use Windows NT groups for authorization, just as with the intranet case, both declaratively and programmatically.

10.8.4.2. Identity management

As long as the principal permission mode is set to PrincipalPermissionMode.UseWindowsGroups, the identity management aspect is just as with the intranet scenario, including the identities of the security call context, as shown in Table 10-4. The main difference between an intranet application and an Internet application that uses Windows credentials is that the client cannot dictate the allowed impersonation level, and the host can impersonate at will. This is because WCF will assign TokenImpersonationLevel.Impersonation to the Windows identity of the security call context.

10.8.5. Using the ASP.NET Providers

By default, WCF role-based security uses Windows user groups for roles and Windows accounts for security identities. There are several drawbacks to this default policy. First, you may not want to assign a Windows account for every client of your Internet application. Second, the security policy is only as granular as the user groups in the hosting domain. Often you do not have control over your end customer's IT department. If you deploy your application in an environment in which the user groups are coarse or in which the user groups don't map well to actual roles users play in your application, or if the group names are slightly different, Windows rolebased security is of little use to you. Roles localization presents yet another set of challenges because role names will differ between customer sites in different locales. Consequently, Internet applications hardly ever use Windows accounts and groups. .NET 2.0 provides out-of-the-box custom credential management called the ASP.NET Provider Model, though non-ASP.NET applications (like WCF applications) can easily use it to authenticate users and authorize them, without ever resorting to Windows accounts.

One of the concrete implementations of the architecture includes a SQL Server store. SQL Server is often the repository of choice for Internet applications and so I will use it in this scenario. To use the SQL Server provider, run the setup file aspnet_regsql.exe found under %Windir%\Microsoft.NET\Framework\<Version>\. The setup program will create a new database called aspnetdb, containing the tables and stored procedures required to manage the credentials.

The SQL Server credential store is well designed, using the latest best practices for credentials management, such as password salting, stored procedures, and so on. This infrastructure helps productivity, saving developers valuable time and effort, not to mention providing a high quality, secure solution. That said, the credential management architecture is that of a provider model, and you can easily add other storage options such as an Access database.

10.8.5.1. The credentials providers

Figure 10-6 shows the architecture of the ASP.NET 2.0 credentials providers.

Figure 10-6. The ASP.NET provider model


Membership providers are responsible for managing users (usernames and passwords), and role providers are responsible for managing roles. Out of the box, ASP.NET offers support for membership stores in SQL Server or Active Directory, and roles can be stored in SQL Server, a file (authorization store), or NT groups (Windows token).

Username and password authentication is done using a class called MembershipProvider from the System.Web.Security namespace, defined as:

 public abstract class MembershipProvider : ProviderBase {    public abstract string ApplicationName    {get;set;}    public abstract bool ValidateUser(string name,string password);    //Additional members } 

MembershipProvider's goal is to encapsulate the actual provider used and the details of the actual data access, as well as to enable changing the membership provider without affecting the application itself. Depending on the configured security provider in the host config file, WCF will use a concrete data access class such as SqlMembershipProvider targeting SQL Server or SQL Server Express:

 public class SqlMembershipProvider : MembershipProvider {...} 

However, WCF interacts only with the MembershipProvider base functionality. WCF obtains the required membership provider by accessing the Provider static property of the Membership class, defined as:

 public sealed class Membership {    public static string ApplicationName    {get;set;}    public static MembershipProvider Provider    {get;}    public static bool ValidateUser(string username,string password);    //Additional members } 

Membership offers many members, which support the many aspects of user management. Membership.Provider retrieves the type of the configured provider from the System.Web section in the host config file. Unspecified, the role provider defaults to SqlMembershipProvider.

Because all membership providers derive from the abstract class MembershipProvider, if you write your own custom credential provider, it needs to derive from MembershipProvider as well.


Since a single credential store can serve many applications, those applications could define the same usernames. To allow for that, every record in the credentials store is scoped by an application name (similar to the way usernames in Windows are scoped by a domain or machine name).

The ApplicationName property of Membership is used to set and retrieve the application name, and the ValidateUser( ) method, which authenticates the specified credentials against the store, and returns true if they match and false otherwise. Membership.ValidateUser is shorthand for retrieving and using the configured provider.

If you configure your application to use the ASP.NET credentials store for authorization and if you enabled roles support, after authentication WCF installs an instance of the internal class RoleProviderPrincipal and attaches it to the thread invoking the operation:

 sealed class RoleProviderPrincipal : IPrincipal {...} 

RoleProviderPrincipal uses the abstract class RoleProvider for authorization:

 public abstract class RoleProvider : ProviderBase {    public abstract string ApplicationName    {get;set;}    public abstract bool IsUserInRole(string username,string roleName);    //Additional members } 

The ApplicationName property of RoleProvider binds the role provider to the particular application. The IsUserInRole( ) method verifies the user's role membership. Like the membership providers, all role providers (including custom role providers) must derive from RoleProvider.

RoleProvider encapsulates the actual provider used, and the role provider to use is specified in the host config file. Depending on the configured role provider, RoleProviderPrincipal uses a corresponding data access class such as SqlRoleProvider to authorize the caller:

 public class SqlRoleProvider : RoleProvider {...} 

You can obtain the required role provider by accessing the Provider static property of the Roles class, defined as:

 public sealed class Roles {    public static string ApplicationName    {get;set;}    public static bool IsUserInRole(string username,string roleName);    public static RoleProvider Provider    {get;}    //Additional members } 

Roles.IsUserInRole( ) is shorthand for accessing Roles.Provider first and then calling IsUserInRole( ) on it. Roles.Provider retrieves the type of the configured provider from the host config file. Unspecified, the role provider defaults to SqlRoleProvider.

10.8.5.2. Credentials administration

If you choose either Windows or Active Directory to store your application's users and roles, then you need to administer the user credentials using the dedicated tools for those stores, such as the Computer Management control panel applet or the Active Directory tools.

If you use SQL Server, .NET 2.0 installs web site administration pages under \Inetpub\wwwroot\aspnet_webadmin\<version number>. Developers can configure the application directly from within Visual Studio 2005. When selecting ASP.NET Configuration from the Web Site menu, Visual Studio 2005 will browse to the ASP.NET administration pages and allow you to configure various parameters, including security configuration. You can configure the following aspects for your application:

  • Create new users and delete existing ones

  • Create new roles and delete existing ones

  • Allocate users to roles

  • Retrieve a user's details

  • Set a user's status

  • Use additional features not relevant to this chapter

10.8.5.3. Shortcomings of Visual Studio 2005

There are a number of significant shortcomings to the Visual Studio 2005-driven administration pages. First, you need Visual Studio 2005. It is unlikely that application or system administrators will have Visual Studio 2005, let alone know how to use it. The administration pages use "/" by default for the application name, and do not offer any visual way to modify that. You must create a web application to activate the administration pages and there is no remote access: the application and Visual Studio 2005 must be co-located so that Visual Studio 2005 can access the application's configuration file. The browser-based user interface is somewhat annoying (you need to frequently click the Back button) and rather dull. Many features that administrators are likely to want to use are not available via the administration pages. This is in spite of the fact that the features are supported by the underlying provider classes. Some of the things missing from the Visual Studio 2005-driven administration pages include the ability to:

  • Update most if not all of the details in a user account

  • Retrieve a user's password

  • Change a user's password

  • Reset a user's password

  • Retrieve information about the number of current online users

  • Remove all users from a role in one operation

  • Retrieve information about the password management policy (such as length, reset policy, type of passwords, etc.)

  • Test user credentials

  • Verify user role membership

Moreover, there are additional features that administrators are likely to want, and yet they are not supported, not even by the provider classes. These features include the ability to retrieve a list of all of the applications in the database, the ability to remove all users from an application, the ability to remove all roles from an application, the ability to delete an application (and all its associated users and roles), and the ability to delete all applications.

10.8.5.4. Credentials Manager

This tools disparity motivated me to develop the Credentials Manager applicationa smart client application that compensates for all of the shortcomings just listed. Figure 10-7 shows a screenshot of Credentials Manager.[*]

[*] I first published an earlier version of Credentials Manager in my article "Manage Custom Security Credentials the Smart (Client) Way," CoDe Magazine, November 2005. Credentials Manager has since become the de facto standard way of administering the ASP.NET SQL Server credentials store.

Figure 10-7. The Credentials Manager utility


Credentials Manager is available with the source code of this book at http://www.oreilly.com/catalog/0596526997

I wrapped the ASP.NET providers with a WCF service (that can be self-hosted or be IIS-hosted), and added the missing features, such as deleting an application.

Credentials Manager uses these endpoints to administer the credential store. In addition, it lets administrators to select at runtime the address of the credentials service, and using the MetadataHelper class presented in Chapter 2 it verifies that the address provided does indeed support the required contracts.

10.8.5.5. Authentication

To authenticate the client's username and password using an ASP.NET provider, set the UserNamePasswordValidationMode property to UserNamePasswordValidationMode.MembershipProvider:

 ServiceHost host = new ServiceHost(typeof(MyService)); host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode =                                 UserNamePasswordValidationMode.MembershipProvider; 

Which provider is used depends on the host config file. In addition, the host config file must contain any provider-specific settings such as a SQL Server connection string, as shown in Example 10-12.

Example 10-12. Internet security using an ASP.NET SQL Server provider

 <connectionStrings>    <add name= "AspNetDb" connectionString = "data source=(local);                          Integrated Security=SSPI;Initial Catalog=aspnetdb"/> </connectionStrings> <system.serviceModel>    <services>       <service name = "MyService" behaviorConfiguration = "ASPNETProviders">          <endpoint             ...          />       </service>    </services>    <behaviors>       <serviceBehaviors>          <behavior name = "ASPNETProviders">             <serviceCredentials>                <userNameAuthentication                   userNamePasswordValidationMode = "MembershipProvider"/>                <serviceCertificate                  ...                />             </serviceCredentials>          </behavior>       </serviceBehaviors>    </behaviors> </system.serviceModel> 

The default application name will be a useless /, so you must set your specific application name. Once ASP.NET providers are configured, WCF initializes the MembershipProvider property of UserNamePasswordServiceCredential with an instance of the configured membership provider. You can programmatically access that membership provider and set its application name:

 ServiceHost host = new ServiceHost(typeof(MyService)); Debug.Assert(host.Credentials.UserNameAuthentication.MembershipProvider != null); Membership.ApplicationName = "MyApplication"; 

You can also configure the application name in the config file, but for that you need to define a custom ASP.NET membership provider, as shown in Example 10-13.

Example 10-13. Configuring the application name for the membership provider

 <system.web>    <membership defaultProvider = "MySqlMembershipProvider">       <providers>          <add name = "MySqlMembershipProvider"            type  = "System.Web.Security.SqlMembershipProvider"             connectionStringName = "AspNetDb"             applicationName = "MyApplication"          />       </providers>    </membership> </system.web> <connectionStrings>    <add name = "AspNetDb"      ...    /> </connectionStrings> 

In Example 10-13, you add a system.Web section with a providers section, where you add a custom membership provider and you set that to be the new default membership provider. Next you need to list the fully qualified type name of the new provider. Nothing prevents you referencing any existing implementation of a membership provider, such as SqlMembershipProvider, as in Example 10-13. When using the SQL provider, you must also list the connection string to use, and you cannot rely on the default connection string from machine.config. Most importantly, set the ApplicationName tag to the desired application name.

10.8.5.6. Authorization

To support authorizing the users, the host must enable role-based security by adding this to the config file:

 <system.web>    <roleManager enabled = "true"/> </system.web> 

To enable the role manager programmatically, you have to use reflection.


Enabling roles this way will initialize the Roles class and have its Provider property set to the configured provider. To use the ASP.NET role provider, set the PrincipalPermissionMode property to PrincipalPermissionMode.UseAspNetRoles:

 ServiceHost host = new ServiceHost(typeof(MyService)); host.Authorization.PrincipalPermissionMode=PrincipalPermissionMode.UseAspNetRoles; 

Or when using a config file, add a custom behavior to that effect:

 <services>    <service name = "MyService" behaviorConfiguration = "ASPNETProviders">       ...    </service> </services> <behaviors>    <serviceBehaviors>       <behavior name = "ASPNETProviders">          <serviceAuthorization principalPermissionMode = "UseAspNetRoles"          ...       </behavior>    </serviceBehaviors> </behaviors> 

After authenticating the client, the RoleProvider property of ServiceAuthorizationBehavior will be set to the configured role provider:

 public sealed class ServiceAuthorizationBehavior : IServiceBehavior {    public RoleProvider RoleProvider    {get;set;}    //More members } 

The default application name will be a useless /, so you must set your specific application name. Unlike the membership provider, you cannot access the RoleProvider property of ServiceAuthorizationBehavior to set the application name, because its value will be null until after authentication. Instead, use the static helper class Roles:

 ServiceHost host = new ServiceHost(typeof(MyService)); Debug.Assert(host.Credentials.UserNameAuthentication.MembershipProvider != null); Roles.ApplicationName = "MyApplication"; 

You can also configure the application name in the config file, but for that you need to define a custom ASP.NET role provider, as shown in Example 10-14.

Example 10-14. Configuring the application name for the role provider

 <system.web>    <roleManager enabled = "true" defaultProvider = "MySqlRoleManager">       <providers>          <add name = "MySqlRoleManager"             type = "System.Web.Security.SqlRoleProvider"             connectionStringName = "AspNetDb"            applicationName = "MyApplication"          />       </providers>    </roleManager> </system.web> <connectionStrings>    <add name = "AspNetDb"      ...    /> </connectionStrings> 

As with the membership provider, you add a system.Web section with a providers section, where you add a custom roles provider and you set that to be the new default role provider. Next you need to list the fully qualified type name of the new provider. As with the membership provider, you can reference any existing implementation of a role provider, such as SqlRoleProvider, in which case you must also list the connection string to use. Finally, set the ApplicationName tag to the desired application name.

10.8.5.7. Declarative role-based security

You can use the PrincipalPermission attribute to verify role membership just as with the intranet scenario because all the attribute does is access the principal object attached to the thread, which has already been set by WCF to RoleProviderPrincipal. Example 10-15 demonstrates declarative role-based security using the ASP.NET providers.

Example 10-15. ASP.NET role provider declarative role-based security

 class MyService : IMyContract {    [PrincipalPermission(SecurityAction.Demand,Role = "Manager")]    public void MyMethod( )    {...} } 

The only noticeable difference between the intranet and Internet scenarios is that in the Internet scenario, when specifying role name, you do not prefix it with the application or domain name.

10.8.6. Identity Management

In the Internet scenario, when using ASP.NET providers, the identity associated with the principal object is a GenericIdentity wrapping the username provided by the client. That identity is considered authenticated. The security call context's primary identity will match the principal identity. The Windows identity, on the other hand, will be set to a Windows identity with a blank username; that is, unauthenticated. Table 10-5 shows the identities in this scenario.

Table 10-5. Identity management in the Internet scenario with ASP.NET providers

Identity

Type

Value

Authenticated

Thread Principal

GenericIdentity

Username

Yes

Security Context Primary

GenericIdentity

Username

Yes

Security Context Windows

WindowsIdentity

-

No


10.8.6.1. Impersonation

Since no valid Windows credentials were provided, the service cannot impersonate any of its clients.

10.8.7. Callbacks

When you use the ASP.NET providers, while the callback message is protected, all calls into the callback object come in with an unauthenticated principal. As a result, the principal identity will be set to a Windows identity with a blank username, which will preclude authorization and role-based security, as it is considered anonymous. While the callback does have a security call context, the Windows identity will be set to a WindowsIdentity instance with a blank identity, which will preclude impersonation. The only meaningful information will be in the primary identity that will be set to an instance of the X509Identity class, with a name set to the common name of the service host certificate suffixed by a thumbprint (a hash) of the certificate:

 class MyClient : IMyContractCallback {    public void OnCallback( )    {       IPrincipal principal = Thread.CurrentPrincipal;       Debug.Assert(principal.Identity.IsAuthenticated == false);       ServiceSecurityContext context = ServiceSecurityContext.Current;       Debug.Assert(context.PrimaryIdentity.Name ==                    "CN=MyServiceCert; D6E33B50BCF6D9609E68762F2C6A14F65679268B");       Debug.Assert(context.IsAnonymous == false);    } } 

I recommend avoiding any sensitive work in the callback, since you cannot easily use role-based security.




Programming WCF Services
Programming WCF Services
ISBN: 0596526997
EAN: 2147483647
Year: 2004
Pages: 148
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net