In the Internet scenario, the clients or services may not be using WCF, or even Windows. If you are writing an Internet service or 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; instead, the application needs to access some custom credentials store. That said, you could still be using Windows security, as demonstrated later.
In an Internet application, you must use Message
security for the transfer security mode to provide for end-to-end
security across all intermediaries. The client should provide
credentials in the form of a username and password, as this is a safe,
low common denominator that all platforms support. For the Internet
scenario, you should use the WSHttpBinding
. You cannot use the basic
binding because it does not provide for username credentials over
Message security. 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 binding.
This is done uniformly across these bindings by selecting MessageCredentialType.Username
for the
client credentials type used with Message security. You need to
configure the bindings this way both at the client and at the
service.
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 Message
Security
Over
Http
:
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 security credentials type of the
WSHttpBinding
is Windows (see
Table 10-3).
Because Message security is the default security mode of the
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;
You can achieve the same configuration using a config file as follows:
<bindings> <wsHttpBinding> <binding name = "UserNameWS"> <security mode = "Message"> <message clientCredentialType = "UserName"/> </security> </binding> </wsHttpBinding> </bindings>
Or, 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
.
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, WSHttp
Security
will use an instance of
NonDualMessageSecurityOverHttp
containing the client credentials type via the MessageCredentialType
enum.
Since in the Internet scenario the client’s message is transferred to the service 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 this option, for a number of reasons. First, there are no guarantees that the password is strong enough, so anyone monitoring the communication could potentially break the encryption using a dictionary attack. Second, this approach forces the service (or more precisely, its host) to have access to the password, thus coupling the host to the credentials store. Finally, while the password may protect the message, it will not authenticate the service to the client.
Instead, to protect the message, WCF uses an X509 certificate. The certificate provides strong protection, and it authenticates the service to the client. A certificate works by using two keys, called the public and private keys, as well as a common name (CN) such as “MyCompanyCert.” What is important about those keys is that anything encrypted with the public key can only be decrypted with the matching 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 to which the host has access. The host makes the certificate (and its public key) publicly available, so 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 uses the public key to encrypt all messages to the service. Upon receiving the encrypted message, WCF decrypts the message on the host side 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. In fact, several calls are made before the first request message from the client to the service, where WCF on the client’s side generates a temporary shared secret it passes encrypted (using the service certificate) to the service. The client and the service will use that shared secret to protect all subsequent communication between them.
The ServiceHostBase
class offers the Credentials
property of the type Service
Credentials
. 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
X509
Certificate
Recipient
Service
Credential
:
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>
The client developer can obtain the service certificate using any out-of-band mechanism (such as 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 services whose certificates are in that folder. In that case, the client will need to obtain the service certificate at runtime as part of the initial pre-call negotiation, check to see whether it is in the Trusted People store, and, if so, proceed to use it to protect the message. This certificate negotiation behavior is the default for the WS bindings. You can disable it and use a hard-configured certificate instead, but for the Internet scenario I strongly recommend using certificate negotiation and storing the certificates in the Trusted People store.
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 client
Credentials
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
present in the client’s Trusted People store. X509CertificateValidationMode.ChainTrust
instructs WCF to trust the certificate if it was issued by a root
authority (such as VeriSign or Thwart) whose certificate is found in
the client’s Trusted Root Authority folder. X509Certificate
Validation
Mode.
ChainTrust
is the
default value used by WCF. X509CertificateValidationMode.PeerOrChainTrust
allows either of those options. Since there are a number of illicit
ways of obtaining a valid certificate from a public
root authority, I do not recommend using this
value. X509Certificate
Validation
Mode.
Peer
Or
Chain
Trust
is available
for tightly controlled environments that purge all public root
authorities and install their own root certificates, which are used
to sign other certificates.
Developers often do not have access to their organizations’
certificates, 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 X509
Certificate
Validation
Mode.
Chain
Trust
by default. You can easily
overcome this by installing the test certificate in the client’s
Trusted People store and using X509
Certificate
Validation
Mode.
Peer
Trust
. The second problem is that WCF by
default expects the service certificate name to match the service
host’s domain (or machine)
name. This provides yet another line of defense, since typically
with an Internet-facing
service, the host domain name will match its certificate common
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>
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 its
username and password to the service, as demonstrated in Example 10-10.
Example 10-10. Providing username and password credentials
MyContractClient proxy = new MyContractClient(); proxy.ClientCredentials.UserName.UserName = "MyUsername"; proxy.ClientCredentials.UserName.Password = "MyPassword"; proxy.MyMethod(); proxy.Close();
The client need not provide a domain name (if Windows security
is used) or application name (if the ASP.NET
providers are used). The host will
use its service domain or a configured application name, as appropriate.
When working with a channel factory instead of a proxy class,
you must 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
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
(available via the Credentials
property of ServiceHost
Base
) provides
the UserNameAuthentication
property
of the type User
Name
Password
Service
Credential
:
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.
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 UserNamePasswordValidation
Mode
to UserNamePasswordValidationMode.Windows
.
Because UserNamePasswordValidation
Mode.
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.
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.
As long as the principal
permission mode is set to Principal
Permission
Mode.
Use
Windows
Groups
, the identity
management aspect of the Internet scenario 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 both use Windows credentials is that with the
latter 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.
By default, role-based security in WCF 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 customers’ IT departments, and if you deploy your application
in an environment in which the user groups are coarse or don’t map
well to the actual roles users play in your application, or if the
group names are slightly different, Windows role-based security will
be of little use to you. Role localization presents yet another set of
challenges, because role names will likely differ between customer
sites in different locales. Consequently, Internet applications hardly
ever use Windows accounts and groups. Out of the box, .NET 2.0 (and
later) provides a custom credential management infrastructure called
the ASP.NET
Providers. Despite its name,
non-ASP.NET
applications (such as
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 ASP.NET
providers includes a SQL Server
store. SQL Server is often the repository of choice for Internet
applications, 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.NETFrameworkv4.0.30319. The
setup program will create a new database called
aspnetdb, containing the tables and stored
procedures required to manage the credentials.
The SQL Server credentials store is well designed and uses the latest best practices for credential management, such as password salting, stored procedures, normalized tables, and so on. In addition to providing a high-quality, secure solution, this infrastructure aids productivity, saving developers valuable time and effort. That said, the credential management architecture is that of a provider model, and you can easily add other storage options if required, such as an Access database.
Figure 10-5 shows the
architecture of the ASP.NET
credentials providers.
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 (the authorization store provider), or NT groups
(the Windows token provider).
Username and password
authentication is done using a class called Membership
Provider
from the
System.Web.Security
namespace,
defined as:
public abstract
class MembershipProvider : ProviderBase
{
public abstract string ApplicationName
{get;set;}
public abstract bool ValidateUser(string username,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 Sql
Membership
Provider
, 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 static 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.
A single credentials store can serve many applications, and those applications may 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 is used to
authenticate the specified credentials against the store, returning
true
if they match and false
otherwise. Membership.
Validate
User()
is shorthand for retrieving
and using the configured provider.
If you have configured your
application to use the ASP.NET
credentials store for authorization and if you enabled roles
support, after authentication WCF will install an instance of the
internal class RoleProviderPrincipal
and attach 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. Just as all membership providers must derive from
MembershipProvider
, 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,
Role
Provider
Principal
uses a
corresponding data access class such as SqlRole
Provider
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 static 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 first accessing Roles.Provider
and then calling IsUserInRole()
on it. Roles.Provider
retrieves the type of the
configured provider from the host config file. If unspecified, the
role provider defaults to SqlRoleProvider
.
If you use SQL Server, .NET installs website administration
pages under Inetpubwwwrootaspnet_webadmin<version
number>. Developers can configure the application
directly from within Visual Studio 2010. When you select ASP.NET
Configuration from the Web Site
menu, Visual Studio 2010 will launch the ASP.NET
development server used for the
administration pages, browse to the ASP.NET
administration pages, and allow
you to configure various parameters, including security. 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
There are a number of significant shortcomings to
using the Visual Studio 2010-driven administration pages. First, you
need Visual Studio 2010. It is unlikely that application or system
administrators will have Visual Studio 2010, 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.
Also, you must create a web application to activate the
administration pages and there is no remote access: the application
and Visual Studio 2010 must be co-located in order for Visual Studio
2010 to be able to access the application’s configuration file, and
the ASP.NET
development server
used for the administration pages cannot accept remote calls. The
browser-based user interface is somewhat annoying (you need to
frequently click the Back button) and rather dull. Furthermore, many
features that administrators are likely to want to use are not
available via the administration pages, despite the fact that the
underlying provider classes support those features. Some of the
things missing from the Visual Studio 2010–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
There are additional features that administrators are likely to want, yet they are not supported even by the provider classes. These features include the ability to retrieve a list of all of the applications in the store, 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.
The IIS7 control panel applet also offers some administrative support for managing the roles and membership providers. However, this support is on a par with that of Visual Studio 2010.
This tools disparity motivated me to develop the Credentials Manager application, a smart client application that compensates for all of the shortcomings just listed. Figure 10-6 shows a screenshot of Credentials Manager.[8]
In Credentials Manager, which is available with
ServiceModelEx, I wrapped the ASP.NET
providers with a WCF service
(which can be self-hosted or IIS 5/6- or WAS-hosted) and added the
missing features, such as the ability to delete an
application.
Credentials Manager uses the dedicated WCF service to
administer the credentials store. In addition, it lets
administrators select the address of the credentials service at
runtime, and using the MetadataHelper
class presented in Chapter 2, it verifies that the address
provided does indeed support the required contracts.
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 assign your application’s name.
Once the ASP.NET
providers are
configured, WCF initializes the Membership
Provider
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";
host.Open();
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>
First, you add a system.Web
section with a providers
section,
where you add a custom membership provider and 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 from
referencing an 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, you must
set the ApplicationName
tag to
the desired application name.
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 Principal
Permission
Mode
property to PrincipalPermissionMode.UseAspNetRoles
:
ServiceHost host = new ServiceHost(typeof(MyService)); host.Authorization.PrincipalPermissionMode = PrincipalPermissionMode.UseAspNetRoles; host.Open();
Alternatively, when using a config file, you can 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
Service
Authorization
Behavior
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 assign your application’s name
using 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
role provider and 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, you must set the
ApplicationName
tag to the
desired application name.
You can use the PrincipalPermission
attribute to verify
role membership just as in the intranet scenario, because all the
attribute does is access the principal object attached to the
thread, which WCF has already set to RoleProviderPrincipal
. Example 10-15 demonstrates
declarative role-based security using the ASP.NET providers.
In the Internet scenario, when you use the ASP.NET
providers, the identity associated
with the principal object is a GenericIdentity
that wraps 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, it is
unauthenticated. Table 10-5 shows the
identities in this scenario.
[8] I first published an earlier version of Credentials Manager in my article “Manage Custom Security Credentials the Smart (Client) Way” (CoDe Magazine, November 2005).
18.223.195.97