Introduction
Windows Communication Foundation (WCF) provides powerful facilities for implementing authorization in services.
This article will show you how to implement custom authentication. Only user with ADMIN role can call the Login()
method.
Note: This article only focuses on checking role of user. You can modify this code for checking username, password and role of user by reading from database.
SERVICE
Create Custom Principal
It is to supply your custom IPrincipal
implementation to WCF. This gives you the chance to implicitly run code after the authentication stage of each request. For this, you have to create your own custom principal and return it to the WCF plumbing. The custom principal will then be available from Thread.CurÂrentPrincipal
to the service code. Custom principals allow full customization of role-based security and expose specialized security logic for the service developer to use.
Writing a custom principal is straightforward. Simply implement the IPrincipal
interface and add your own custom functionality. To integrate the principal with WCF, you have to set the PrincipalÂPerÂmissionMode
attribute in the ServiceAuthorization
element to "custom
" and provide an authorization policy that is responsible for creating the principal and giving it back to WCF.
An IPrincipal
-derived class must implement a single method called IsInRole
where you can pass in a role name and get a Boolean response. In addition, the principal has a reference to the identity class it wraps.
class CustomPrincipal : IPrincipal
{
public bool IsInRole(string role)
{
EnsureRoles();
return _roles.Contains(role);
}
protected virtual void EnsureRoles()
{
if (_identity.Name == "AnhDV")
_roles = new string[1] { "ADMIN" };
else
_roles = new string[1] { "USER" };
}
}
Create Custom Authorization Policy
An authorization policy is simply a class that implements the System.IdentityModel.Policy.IAuthorizationPolicy
interface. Implementations of this interface must employ a method called Evaluate
, which gets called on every request. Here you can reach into the WCF service security context and set the custom principal. The code below shows a custom principal as well as the authorization policy and its corresponding configuration entries.
The important part of AuthorizationPolicy:IAuthorizationPolicy
is the Evaluate()
method which, using the context of the claim evaluation, trys to get the PrimaryIdentity
. This property represents the identity discovered by WCF during user credentials validation. The method then converts this to an CustomPrincipal
and attaches it to the current context thus making the principal available on the current running thread.
class AuthorizationPolicy : IAuthorizationPolicy
{
Guid _id = Guid.NewGuid();
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
IIdentity client = GetClientIdentity(evaluationContext);
evaluationContext.Properties["Principal"] = new CustomPrincipal(client);
return true;
}
private IIdentity GetClientIdentity(EvaluationContext evaluationContext)
{
object obj;
if (!evaluationContext.Properties.TryGetValue("Identities", out obj))
throw new Exception("No Identity found");
IList<IIdentity> identities = obj as IList<IIdentity>;
if (identities == null || identities.Count <= 0)
throw new Exception("No Identity found");
return identities[0];
}
}
<serviceAuthorization principalPermissionMode="Custom">
<authorizationPolicies>
<add policyType="WikiService.AuthorizationPolicy, App_Code/WikiSecurity" />
</authorizationPolicies>
</serviceAuthorization>
Create Custom Validator
If left to itself, the service will now try and authenticate the supplied credentials against Windows user accounts and, as we are using our own custom authentication, this will fail!
The password stuff lives in the following override, I call my security module to check the user and password against the db:
public class CustomValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (string.IsNullOrEmpty(userName))
throw new ArgumentNullException("userName");
if (string.IsNullOrEmpty(password))
throw new ArgumentNullException("password");
}
}
We indicate to the service that we want our own class, 'CustomValidator
' which lives in the assembly 'WikiSecurity
', to do the password stuff using this config:
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="CustomValidator, App_Code/WikiSecurity" />
Create Certificate
You are able to send your username and password over the wire if you use the following config.
NOTE: You will need to create and install a suitable certificate on the server side to support this, another story...
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
You must open \Certificate Setup Application\Certificate Setup\Source Code\Certificate Setup.sln and run it to create your temp certificate example: g2-anhdv-xp.fsoft.fpt.vn
And modify your config:
<serviceCertificate findValue="g2-anhdv-xp.fsoft.fpt.vn"
x509FindType="FindBySubjectName" />
Your Service Code: Example WikiSecurity Class
Only user with ADMIN role can call the Login()
method of the WikiSecurity service:
[PrincipalPermission(SecurityAction.Demand, Role = "ADMIN")]
public void Login()
{
}
CLIENT
The username and password can then be set on your client proxy something like:
var factory = new ChannelFactory<IWikiSecurity>("*");
factory.Credentials.UserName.UserName = "AnhDV";
factory.Credentials.UserName.Password = "anhdv";
var wikiProxy = factory.CreateChannel();
wikiProxy.Login();
var factory = new ChannelFactory<IWikiSecurity>("*");
factory.Credentials.UserName.UserName = "AnhDV1";
factory.Credentials.UserName.Password = "anhdv";
var wikiProxy = factory.CreateChannel();
wikiProxy.Login();
Hope this helps.
Enjoy and have fun.
History
- 4th March, 2009: Initial post