How can I use the Role Manager in a WCF Service?
In my .NET Application, I can restrict a class or a method with the [Authorize(Roles=)]
tag. How can I enable this for my WCF Service?
I currently have the following binding set for each endpoint:
<webHttpBinding>
<binding name="TransportSecurity" maxReceivedMessageSize="5242880">
<security mode="Transport">
<transport clientCredentialType="None"/>
</security>
</binding>
</webHttpBinding>
Since I want to have the user log in and receive a cookie with the principal, do I need to change this to another sort of clientCredentialType
?
Edit 1:
This is using REST, not SOAP. It is also to note, that it is important that it works with mobile devices (android, iPhone) and can use cookies to maintain a session. So far, I have been unable to get this working, using the following code/config:
Config File:
<roleManager enabled="true" defaultProvider="ActiveDirectoryRoleProvider" cacheRolesInCookie="true" cookieName="RoleCookie" cookiePath="/" cookieTimeout="30" cookieRequireSSL="false" cookieSlidingExpiration="true" createPersistentCookie="false" cookieProtection="All">
<providers>
<clear />
<add name="ActiveDirectoryRoleProvider" connectionStringName="ADServices" connectionUsername="" connectionPassword="" attributeMapUsername="sAMAccountName" type="" />
</providers>
</roleManager>
<membership defaultProvider="MembershipADProvider">
<providers>
<add name="MembershipADProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" applicationName="" connectionStringName="ADServices" connectionUsername="" connectionPassword="" attributeMapUsername="sAMAccountName" />
</providers>
</membership>
<bindings>
<webHttpBinding> <!-- webHttpBinding is for REST -->
<binding name="TransportSecurity" maxReceivedMessageSize="5242880">
<security mode="Transport">
</security>
</binding>
</webHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="web">
<webHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="ServiceBehaviour">
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />
<serviceDebug httpHelpPageEnabled="true" includeExceptionDetailInFaults="true" />
<serviceAuthorization principalPermissionMode="UseAspNetRoles" roleProviderName="ActiveDirectoryRoleProvider" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="MembershipProvider" membershipProviderName="MembershipADProvider" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
Code
public void SignIn2(string userName, bool createPersistentCookie)
{
if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
// put the attributes in a string for userdata
string userData = "";
// create the ticket
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1,
userName,
DateTime.Now,
DateTime.Now.AddMinutes(240),
createPersistentCookie,
userData);
// Now encrypt the ticket.
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
// Create a cookie and add the encrypted ticket to the cookie as data.
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
// add the cookie
HttpContext.Current.Response.Cookies.Add(authCookie);
}
Now using the Principal Permission, I get a SecurityException
(I know the role is valid on the server)
[PrincipalPermission(SecurityAction.Demand, Role = Constants.RoleUser)]
public Message TestRoles()
{
var context = NetworkHelper.GetWebOperationContext();
return context.CreateTextResponse("You have successfully activated the endpoint.");
}
Am I missing a crucial step here?
I wrote a blog post about how to using ASP.NET authentication with WCF; the gist of it is that you want to use the following binding:
<basicHttpBinding>
<binding>
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
</security>
</binding>
</basicHttpBinding>
You must also apply the following serviceBehavior
<behavior>
<!-- no need for http get;
but https get exposes endpoint over SSL/TLS-->
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true"/>
<!-- the authorization and credentials elements tie
this behavior (defined as the default behavior) to
the ASP.NET membership framework-->
<serviceAuthorization
principalPermissionMode="UseAspNetRoles"
roleProviderName="AspNetRoleProvider" />
<serviceCredentials>
<userNameAuthentication
userNamePasswordValidationMode="MembershipProvider"
membershipProviderName="AspNetMembershipProvider" />
</serviceCredentials>
</behavior>
An important point to note is that you must use SSL if you're going to secure WCF with a name and password, that's why transport security is specified.
Once you've done this, you should be able to use the PrincipalPermission
attribute to secure your service methods.
I had the similar problem with the principal, a long time ago. I don't remember the details, but try this, from my very old project:
i. You have to add 2 helper classes:
using System.Web;
using System.IdentityModel.Claims;
using System.IdentityModel.Policy;
namespace TicketingCore
{
public class HttpContextPrincipalPolicy : IAuthorizationPolicy
{
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
HttpContext context = HttpContext.Current;
if (context != null)
{
evaluationContext.Properties["Principal"] = context.User;
}
return true;
}
public System.IdentityModel.Claims.ClaimSet Issuer
{
get { return ClaimSet.System; }
}
public string Id
{
get { return "TicketingCore HttpContextPrincipalPolicy"; }
}
}
}
using System;
using System.Collections.Generic;
using System.IdentityModel.Claims;
using System.IdentityModel.Policy;
using System.Text;
using System.Web;
using System.Security.Principal;
namespace TicketingCore
{
// syncs ServiceSecurityContext.PrimaryIdentity in WCF with whatever is set
// by the HTTP pipeline on Context.User.Identity (optional)
public class HttpContextIdentityPolicy : IAuthorizationPolicy
{
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
HttpContext context = HttpContext.Current;
if (context != null)
{
// set the identity (for PrimaryIdentity)
evaluationContext.Properties["Identities"] =
new List<IIdentity>() { context.User.Identity };
// add a claim set containing the client name
Claim name = Claim.CreateNameClaim(context.User.Identity.Name);
ClaimSet set = new DefaultClaimSet(name);
evaluationContext.AddClaimSet(this, set);
}
return true;
}
public System.IdentityModel.Claims.ClaimSet Issuer
{
get { return ClaimSet.System; }
}
public string Id
{
get { return "TicketingCore HttpContextIdentityPolicy"; }
}
}
}
ii. Change the webconfig to add these 2 policy, here's my config (format: add policyType="namespace.class, assembly" )
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="MembershipProvider" membershipProviderName="SQLMembershipProvider"/>
</serviceCredentials>
<serviceAuthorization principalPermissionMode="Custom">
<authorizationPolicies>
<add policyType="TicketingCore.HttpContextIdentityPolicy, TicketingCore"/>
<add policyType="TicketingCore.HttpContextPrincipalPolicy, TicketingCore"/>
</authorizationPolicies>
</serviceAuthorization>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
iii. Ensure the cookie and role are working fine
Note: I don't remember the source of the solution as well, you may need to google the class name to find out, hope the will be helpful to you, good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With