Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Custom Authorization in WCF

4.86/5 (26 votes)
4 Mar 2009CPOL3 min read 199.1K   7K  
This article will show you how to implement custom authentication

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. 

C#
class CustomPrincipal : IPrincipal
{
public bool IsInRole(string role)
    {
        EnsureRoles();

        return _roles.Contains(role);
    }

    // read Role of user from database
    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. 

C#
class AuthorizationPolicy : IAuthorizationPolicy
{
    Guid _id = Guid.NewGuid();

    // this method gets called after the authentication stage
    public bool Evaluate(EvaluationContext evaluationContext, ref object state)
    {
        // get the authenticated client identity
        IIdentity client = GetClientIdentity(evaluationContext);

        // set the custom principal
        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];
    }
} 
XML
 <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:

C#
public class CustomValidator : UserNamePasswordValidator
{
    public override void Validate(string userName, string password)
    {
        // validate arguments
        if (string.IsNullOrEmpty(userName))
            throw new ArgumentNullException("userName");
        if (string.IsNullOrEmpty(password))
            throw new ArgumentNullException("password");

        // check the user credentials from database
        //int userid = 0;
        //CheckUserNameAndPassword(userName, password, out userid);
        //if (0 == userid)
        //throw new SecurityTokenException("Unknown username or 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:   

XML
<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... 

XML
<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: 

XML
<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: 

C#
[PrincipalPermission(SecurityAction.Demand, Role = "ADMIN")]
public void Login()
{

} 

CLIENT

The username and password can then be set on your client proxy something like:

C#
var factory = new ChannelFactory<IWikiSecurity>("*");
factory.Credentials.UserName.UserName = "AnhDV";//Do Viet Anh
factory.Credentials.UserName.Password = "anhdv";
var wikiProxy = factory.CreateChannel();
wikiProxy.Login();//-->Successful

var factory = new ChannelFactory<IWikiSecurity>("*");
factory.Credentials.UserName.UserName = "AnhDV1";//Do Viet Anh 1
factory.Credentials.UserName.Password = "anhdv";
var wikiProxy = factory.CreateChannel();
wikiProxy.Login();//Access Denied

Hope this helps.

Enjoy and have fun.

History

  • 4th March, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)