Team blog of MCS @ Middle East and Africa

This blog is created by Microsoft MEA HQ near shoring team, and it aims to share knowledge with the IT community.With its infrastructure and development sides,It brings to you the proven best practices and real world experiences from Subject Matter Experts
Follow Us On Twitter! Subscribe To Our Blog! Contact Us

Implementing Dynamic Authorization for a WCF service using SQL providers

Implementing Dynamic Authorization for a WCF service using SQL providers

  • Comments 7
  • Likes

Using the ASP.NET SQL membership provider to authenticate and authorize calls to WCF services is not an uncommon scenario. But the problem is in the authorization part. Usually to authorize access to WCF service methods this is done using static hard coded attributes decorating the methods definition. So basically you would say that this method is accessible to anyone in the specific role. This is shown below.

        [PrincipalPermission(SecurityAction.Demand, Role = "Managers")]

        public string GetData(int value)

        {

            return string.Format("You entered: {0}", value);

        }

Now the more challenging scenario is when you need to configure the access rules in the runtime like from a database or configuration files rather than in the service code.

To implement this scenario you would need to implement a custom WCF service behaviour along with a custom service authorization manager.

First the behaviour goes like this.

public class SqlProviderSecurityBehavior : IServiceBehavior

{

    private ISqlProviderSecuritySettings settings;

    public SqlProviderSecurityBehavior(ISqlProviderSecuritySettings settings)

    {

        this.settings = settings;

    }

    #region IServiceBehavior Members

 

    public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)

    {

 

        if (this.settings.MembershipProvider != null)

        {

            ServiceCredentials cr = bindingParameters.Find<ServiceCredentials>();

            if (cr == null)

            {

                cr = new ServiceCredentials();

                bindingParameters.Add(cr);

            }

 

            // set membership provider

            SqlMembershipProvider sqlMembership = new SqlMembershipProvider();

            NameValueCollection config = new NameValueCollection();

 

            //if specifying the actual connection string, use reflection to set the values

            if (!string.IsNullOrEmpty(this.settings.MembershipProvider.ConnectionString))

            {

                Type t = typeof(SqlMembershipProvider);

                FieldInfo fi = t.GetField("_sqlConnectionString", BindingFlags.NonPublic | BindingFlags.Instance);

                fi.SetValue(sqlMembership, this.settings.MembershipProvider.ConnectionString);

                sqlMembership.ApplicationName = this.settings.MembershipProvider.ApplicationName;

            }

            else

            {

                //use Initialize method when specyfing connectionName... no reflection needed

                config.Add("connectionStringName", this.settings.MembershipProvider.ConnectionStringName);

                config.Add("applicationName", this.settings.MembershipProvider.ApplicationName);

                sqlMembership.Initialize("SqlMembershipProvider", config);

            }

            cr.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.MembershipProvider;

            cr.UserNameAuthentication.MembershipProvider = sqlMembership;

        }

 

        if (this.settings.RoleProvider != null)

        {

            // set role provider

            serviceHostBase.Authorization.PrincipalPermissionMode = PrincipalPermissionMode.UseAspNetRoles;

            SqlRoleProvider sqlRoleProvider = new SqlRoleProvider();

            NameValueCollection config = new NameValueCollection();

 

            //if specifying the actual connection string, use reflection to set the values

            if (!string.IsNullOrEmpty(this.settings.RoleProvider.ConnectionString))

            {

                Type t = typeof(SqlRoleProvider);

                FieldInfo fi = t.GetField("_sqlConnectionString", BindingFlags.NonPublic | BindingFlags.Instance);

                fi.SetValue(sqlRoleProvider, this.settings.RoleProvider.ConnectionString);

                sqlRoleProvider.ApplicationName = this.settings.RoleProvider.ApplicationName;

            }

            else

            {

                //use Initialize method when specyfing connectionName... no reflection needed

                config.Add("connectionStringName", this.settings.RoleProvider.ConnectionStringName);

                config.Add("applicationName", this.settings.RoleProvider.ApplicationName);

                sqlRoleProvider.Initialize("SqlRoleProvider", config);

            }

            serviceHostBase.Authorization.RoleProvider = sqlRoleProvider;

            serviceHostBase.Authorization.ServiceAuthorizationManager = new SqlAuthorizationManager(sqlRoleProvider);

 

            // store as service extension

            RoleProviderServiceHostExtension ext = new RoleProviderServiceHostExtension(sqlRoleProvider);

            serviceHostBase.Extensions.Add(ext);

        }

    }

 

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)

    {

    }

 

    public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)

    {

    }

 

    #endregion

}

As you can see in this WCF behaviour it sets the service authorization manager to be our custom class that is implemented as follows.

public class SqlAuthorizationManager : ServiceAuthorizationManager

{

    private SqlRoleProvider sqlRoleProvider;

    public SqlAuthorizationManager(SqlRoleProvider _sqlRoleProvider) : base()

    {

        sqlRoleProvider = _sqlRoleProvider;

    }

    protected override bool CheckAccessCore(OperationContext operationContext)

    {

        bool baseResult = base.CheckAccessCore(operationContext);

        //For mex support (starting WCF service, etc.)    

        //NOTE: Other than for service startup this will NOT be true because the WCF    

        //configuration dictates that WindowsCredentials must be sent and Anonymous users    

        //are NOT allowed.    

        if (operationContext.ServiceSecurityContext.IsAnonymous) return true;

        //Extract the identity token of the current context user making the call to this service        

        IIdentity Identity = operationContext.ServiceSecurityContext.PrimaryIdentity;

        //Prior to proceeding, throw an exception if the user has not been authenticated at all        

        if (!Identity.IsAuthenticated)

        {

            throw new SecurityTokenValidationException("Authenticated user is required to call this service.");

        }

        string[] roles = sqlRoleProvider.GetRolesForUser(Identity.Name);

        if (roles.Length <= 0)

        {

            throw new System.ServiceModel.Security.SecurityAccessDeniedException("User is not part of the service account role.");

        }

        if (!roles.Contains("The role you need to check comes here or can be dynamic"))

        {

            throw new System.ServiceModel.Security.SecurityAccessDeniedException("User is not part of the service account role.");

        }

        //this is the custom authorization rules in a custom table of your choosing

        ASPProvidersEntities entities = new ASPProvidersEntities();

        var userrule = (from serviceAuth in entities.ServiceAuthorizations

                        where serviceAuth.ServiceContractName == operationContext.EndpointDispatcher.ContractName && serviceAuth.Username == Identity.Name

                        select serviceAuth).SingleOrDefault();

        if (userrule == null)

        {

            throw new System.ServiceModel.Security.SecurityAccessDeniedException("User is not authorized to call this service.");

        }

        return baseResult;

    }

}

Happy coding :)

Comments
  • Hi Malek

    Good post

    Helped me a lot

    But im not able get what is

    RoleProviderServiceHostExtension

    ASPProvidersEntities

    Please help me on this!!

    -Praveen

  • Also ISqlProviderSecuritySettings not able to recognize by .net

  • Hi,

    A legislative act authorizing  money to be spent for government programs that specifies a maximum spending level without provision for actual funds. thanks for sharing...

  • Hi Praveen,

    All these are custom implementation specific classes. So they implement the application logic itself and not standard .net classes. For example in my case the ASPProvidersEntities is an entity based class that maps to a SQL custom database table were I keep the authorization rules for the users. So it is a simple table with mapping between the WCF contract name and the username.

    I hope this gives you some help.

    mmalek

  • Hi Malek,

    I understood that later, when i examined the code.

    Thanks Again! For your post and Comment!!

  • Nice Tutorial. I implement our site

  • Nice Tutorial. I implement our site

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment