RESTful WCF service security - wcf

How we could authenticate/authorize WCF RESTful service (that uses webHttpBinding (and not wsHttpBinding, like in SOAP case))?
I.e. we want to use Membership/Roles to permit (or prohibit) user consume each web method according his role.
Thanks in advance.
Ilan.

You can use certificates to secure the service or send the username and password in the header. You can then add a behavior by implementing IAuthorizationPolicy to the service so that you don't have to implement the security check in every web service method that you expose.
public class CertificateAuthorizationPolicy : IAuthorizationPolicy
{
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
IIdentity identity;
object untypedIdentities;
if (!evaluationContext.Properties.TryGetValue("Identities", out untypedIdentities))
{
identity = null;
return false;
}
var identities = (IEnumerable<IIdentity>)untypedIdentities;
identity = identities.Where(item => item.AuthenticationType == "X509").FirstOrDefault();
var claimSet = (X509CertificateClaimSet)evaluationContext.ClaimSets[0];
var certificate = claimSet.X509Certificate;
}
In web.config you tell the service to use the authorization policy
<behavior name="CertificateSecurityServiceBehavior">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceAuthorization principalPermissionMode="Custom">
<authorizationPolicies>
<add policyType="CertificateAuthorizationPolicy, MyAssembly.Security" />
</authorizationPolicies>
</serviceAuthorization>
<dataContractSerializer maxItemsInObjectGraph="2147483647" />
</behavior>
Another option is to setup SSL on the IIS Server so that it requires SSL and client certificate to connect to any page.

Related

AuthorizeAttribute in WCF as MVC's

I'm new to WCF (Most of my time, I worked with ASP.NET Web API & MVC).
I wonder there is any AuthorizeAttribute in WCF or not (custom implementation is ok) .
For example:
+) In Web API, I have these steps :
-) Call [POST] api/login with email & password to login
-) Store email & password in front-end site, each time front-end sends a request, they have to include email & password in header for authentication.
-) In back-end, AuthorizeAttribute reads email & password of request header, do validation then authenticate the request as it is valid.
My question is:
Can my WCF application have an Attribute to do the same work as the API does ?
Thank you
You could implement your custom UserNamePasswordValidator:
public class MyValidator : UserNamePasswordValidator
{
public override void Validate(string user, string password)
{
// Do your validation logic.
// In case of unauthorized access throw appropriate exception.
}
}
And configure service behavior to use it:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="behavior_name">
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="SomeNamespace.MyValidator, AssemblyName" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Also it is not a good idea to store password. Would be better to generate an auth token by this login & password, and use that token.

IDispatchMessageInspector and Thread.CurrentPrincipal

I have implemented a custom IDispatchMessageInspector, in order to parse one custom token type. After parsing the token I assign:
ServiceSecurityContext.Current.AuthorizationContext.Properties["ClaimsPrincipal"] = claimsPrincipal;
ServiceSecurityContext.Current.AuthorizationContext.Properties["Identities"] = identities;
Thread.CurrentPrincipal = claimsPrincipal;
I thought after ClaimsPrincipal got assigned in my IDispatchMessageInspector, It should have been available in my service method, unfortunately I've got a WindowsPrincipal(IsAuthentificated = false) there.
var currentIdentity = Thread.CurrentPrincipal as ClaimsPrincipal;
any thoughts?
Edit:
My web.config:
<services>
<service name="EchoService.TestEchoService">
<endpoint address="api" bindingConfiguration="secured" binding="webHttpBinding" behaviorConfiguration="rest" contract="EchoService.IEchoService"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceCredentials useIdentityConfiguration="true">
</serviceCredentials>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="rest">
<webHttp helpEnabled="true" automaticFormatSelectionEnabled="true"/>
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment>
<serviceActivations>
<add relativeAddress="echo.svc" factory="System.ServiceModel.Activation.ServiceHostFactory" service="EchoService.TestEchoService"/>
</serviceActivations>
</serviceHostingEnvironment>
</system.serviceModel>
<system.identityModel>
<identityConfiguration>
<securityTokenHandlers>
<clear/>
<add type="EchoService.Host.Tokens.SimpleWebTokenHandler,EchoService.Host"></add>
</securityTokenHandlers>
<audienceUris>
<clear/>
<add value="http://securitytestrealm/"/>
</audienceUris>
<issuerTokenResolver type="System.IdentityModel.Tokens.NamedKeyIssuerTokenResolver,System.IdentityModel.Tokens.Jwt">
<securityKey symmetricKey="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=" name="YYYYYYYYYYYYYYYYYYY" />
</issuerTokenResolver>
</identityConfiguration>
Edit2:
Call sequence:
Contructor => GetTokenTypeIdentifiers => TokenType
In GetTokenTypeIdentifiers I return:
return new string[] { "http://schemas.microsoft.com/2009/11/identitymodel/tokens/swt" };
This sequence takes only place if I call my service first time.
The interesting thing that none of Handlers methods are being called when it is called after that.
Tokens should not be handled in IDispatchMessageInspector.
You need to implement SecurityTokenHandler that will allow you to read Token and extract whatever it carries => convert it to collection of claims and then return that collection. Provided claims collection will automatically be used to create ClaimsPrincipal by the WCF pipeline.
Check the link below:
http://msdn.microsoft.com/en-us/library/system.identitymodel.tokens.securitytokenhandler.validatetoken.aspx
EDIT:
You have two possible approaches to add token handler to pipeline. One is to implement custom service host:
public class CustomServiceHost : ServiceHost
{
protected override void OnOpening()
{
base.OnOpening();
IdentityConfiguration identityConfiguration = new IdentityConfiguration();
identityConfiguration.SecurityTokenHandlers.Clear();
identityConfiguration.SecurityTokenHandlers.AddOrReplace(new CustomSecurityTokenHandler());
}
}
or via same xml segments in web.config:
http://msdn.microsoft.com/en-us/library/hh568671.aspx
EDIT:
ServiceCredentials credentials = this.Description.Behaviors.Find<ServiceCredentials>();
if (credentials == null)
{
credentials = new ServiceCredentials();
this.Description.Behaviors.Add(credentials);
}
credentials.UseIdentityConfiguration = true;
I was able to sort this thing out.
The only thing that was missing is one setting in web.config.
<serviceBehaviors>
<behavior>
<serviceAuthorization principalPermissionMode="None" />
Now it works as expected. Is there any security flaws?
Sorry, that's not how authentication in WCF works. You cannot simply assign Thread.CurrentPricipal from somewhere in the WCF processing pipeline and assume that WCF will automagically pickup that as a fact proving the user is authenticated.
You will need to hook into the right place of the pipeline. Which would be a serviceCredentials behavior.

Pass login information with each WCF Webservice call?

Hi,
I have set a WCF up as a webservice(percall), this webservice will getting request from a wide range of systems. Now we need somekind of way to identify the client.
Its possible to build a CustomUserNamePasswordValidation but this demands a secured(SLL) communication. In our case we do not need the security and the solution needs to be as easy as possible to setup.
So the question is how to send client identifikation(username/password) on each call?
I could place the identifikation data in the header but Im not sure how this can be tested with example soupUI? And Im not sure if all systems that will be communicate can handle this without complications?
Any sugestions?
Please note: I do only want to do 1 call, so no login service method should have to be used.
WCF do not suport sending user credentials unsecured. To solve this you could use the clear username binding or adding the credentials manually in the heador of the message(this is simple with WCF)
Define a binding in the web.config like :
<basicHttpBinding>
<binding name="BasicAuthBinding">
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</basicHttpBinding>
Then define a service behaviour like :
<behavior name="Namespace.TestBehaviour">
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="Namespace.ServiceSecurity.UserAuthenticator, Namespace" />
</serviceCredentials>
<serviceAuthorization>
<authorizationPolicies>
<add policyType="Namespace.ServiceSecurity.MyAuthorizationPolicy, Namespace" />
</authorizationPolicies>
</serviceAuthorization>
</behavior>
Then provide the custom authentication and authorization classes as follows :
public class MyAuthorizationPolicy: IAuthorizationPolicy
{
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
IList<IIdentity> identities = (IList<IIdentity>) evaluationContext.Properties["Identities"];
foreach (IIdentity identity in identities)
{
if (identity.IsAuthenticated &&
identity.AuthenticationType == "UserAuthenticator")
{
evaluationContext.Properties["Principal"] = identity.Name;
return true;
}
}
if (!evaluationContext.Properties.ContainsKey("Principal"))
{
evaluationContext.Properties["Principal"] = "";
}
return false;
}
public ClaimSet Issuer
{
get { throw new NotImplementedException(); }
}
}
authentication as follows :
public class UserAuthenticator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
//authenticate me however you want
//then set whatever you want
}
}
If you need further security, change basicHttpBinding to a wsHttpBinding and use a certificate
EDIT : Almost forgot, use the defined service behaviour and binding in your service interface definition in web.config.
In the code :
public class WCF_Project_Authentification : UserNamePasswordValidator
{
#region UserNamePasswordValidator Interface Member
public override void Validate(string userName, string password)
{
if (userName != "Jeanc" || password != "fortin")
{
throw new FaultException("Authentification failed");
}
}
#endregion
}
In the config :
<behaviors>
<serviceBehaviors>
<behavior name="Service_Behavior">
<serviceMetadata httpGetEnabled="False" httpsGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="True"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="WcfService.Authentification.WCF_Project_Authentification, WcfService"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
And in the client code :
proxy.ClientCredentials.UserName.UserName = "Jeanc";
proxy.ClientCredentials.UserName.Password = "fortin";

Custom ASP.NET Forms Authentication Service with WCF

I am trying to create a custom ASP.NET Forms Authentication Service using WCF. I am calling it via a test page that contains only a single line of JS (except for the ScriptManager scripts). The problem is that the server returns response code 500 and the response body is empty. My breakpoints in the service method and in the Application_Error in Global.asax are not being hit.
Sys.Services.AuthenticationService.login('user', 'pass', false, null, null, null, null, null);
I can see the request go to the server in the browser tools with the following request body:
{"userName":"user","password":"pass","createPersistentCookie":false}
Other things on the request side also seem fine.
Here is the configuration service:
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="BtxAuthenticationEndpointBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
<serviceBehaviors>
</serviceBehaviors>
</behaviors>
<services>
<service name="MyNamespace.BtxAuthenticationService">
<endpoint contract="MyNamespace.IBtxAuthenticationService" binding="webHttpBinding" behaviorConfiguration="BtxAuthenticationEndpointBehavior"/>
</service>
</services>
</system.serviceModel>
And the declaration of the interface:
[ServiceContract]
public interface IBtxAuthenticationService
{
[OperationContract]
[WebInvoke]
bool Login(string username, string password, bool createPersistentCookie);
[OperationContract]
[WebInvoke]
void Logout();
}
The implementation:
public class BtxAuthenticationService : IBtxAuthenticationService
{
public bool Login(string username, string password, bool createPersistentCookie)
{
... irrelevant because this is never hit
}
public void Logout()
{
}
}
Can someone tell me how to configure this or point me to a way to debug it. An article about implementing custom Forms Authentication with a WCF service will be welcome too. I've tried experimenting with various other settings including all the exception details settings I could find but could not make any progress (though I was able to make some regress and get different exceptions like missing endpoints and so on).
Thank you for your time.
Not sure if this helps. I have never written such service but your configuration creates WCF service wich is not ASP.NET AJAX ready and works with XML instead of JSON. Try to use this instead of webHttp behavior:
<endpointBehaviors>
<behavior name="BtxAuthenticationEndpointBehavior">
<enableWebScript />
</behavior>
</endpointBehaviors>

Why isn't the Identities property passed to my custom IAuthorisationPolicy?

I'm trying to set up a custom IAuthorisationPolicy that I can give to ServiceAuthorizationBehaviour to install my own IPrincipal implementation. I've followed the instructions here, and written a test that verifies that this works when self-hosting using a NetNamedPipes binding.
The problem is, when I try to use this hosted under IIS the Identities property is not being set in the evaluationContext that is passed to my IAuthorisationPolicy (whereas it is when self-hosting).
The following is an extract from my configuration file:
<customBinding>
<binding name="AuthorisedBinaryHttpsBinding" receiveTimeout="00:03:00" sendTimeout="00:03:00">
<security authenticationMode="UserNameOverTransport">
</security>
<binaryMessageEncoding>
</binaryMessageEncoding>
<httpsTransport />
</binding>
</customBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="CommonServiceBehaviour">
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="MembershipProvider"
membershipProviderName="AdminSqlMembershipProvider"/>
</serviceCredentials>
<serviceMetadata httpGetEnabled="true" />
<dataContractSerializer maxItemsInObjectGraph="2147483647" />
</behavior>
</serviceBehaviors>
</behaviors>
(Note that I'm configuring the ServiceAuthorisationBehavior through code, which is why it doesn't appear here)
Any idea why I'm not being passed the Identities property?
My IAuthorisationPolicy looks like this:
public class PrincipalInstallingAuthorisationPolicy : IAuthorizationPolicy
{
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
var identity = GetClientIdentity(evaluationContext);
if (identity == null)
{
return false;
}
// var groups = get groups
var principal = new GenericPrincipal(identity, groups);
evaluationContext.Properties["Principal"] = principal;
return true;
}
private IIdentity GetClientIdentity(EvaluationContext evaluationContext)
{
object obj;
if (!evaluationContext.Properties.TryGetValue("Identities", out obj))
{
return null;
}
IList<IIdentity> identities = obj as IList<IIdentity>;
if (identities == null || identities.Count <= 0)
{
return null;
}
return identities[0];
}
...
}
I was working on the same issue. It may be because you use HTTP. In my case it is okay with the IIS and TCP binding. When i changed it to the basicHttpBinding the identitiy stoped to be sent.
One thing to check - do you really set the serviceAuthorization's principalPermissionMode to "Custom" (as shown in the article you linked to)? That's crucial for your success.
<serviceAuthorization principalPermissionMode="Custom">
Also - could it be that you're calling your service as an anonymous user? In that case, you might get a "null" identity.
What's you client config like?? What bindings do you use, what security settings?
Marc