Custom Authenticating JSON, SOAP and WSHTTP endpoints to same service


  • I've been agonising over this for the past week, and cannot seem to come up with a solution that I'm happy with.  It seems to me that the WCF security model is not able to provide the flexibility I need - but I would really appreciate any advice on this.

    I'm proof of concepting an ESB framework for our organisation, which not only includes a design for the vanilla .Net assemblies which provide business-layer functionality, but also public WCF services to do the same.  The scenarios for use of these services are:

    a) Internal developers working on AJAX components for our own site.
    b) Internal developers working on Silverlight projects for our own site.
    c) Internal/External developers working on non-Asp.Net AJAX components (e.g. desktop sidebar gadgets - accessing the JSON services with jQuery or similar).
    d) B2B scenarios - Whole-organisation licensing of the services.
    e) Internal apps running in a non-internal environment (in another country, for example, where direct access to the DB is not available, or not advisable).

    For the internal developer scenarios, specialised client libraries will be written (i.e. a js file, Silverlight Assemblies, and a wsHttp client wrapper) to make the client consuming code appear as close as possible to the code that would be written against the Vanilla libraries.

    Clearly, the minimum three endpoints I need to be looking at are WebHttp, BasicHttp and WsHttp (there is an argument for Tcp for scenario 'e' - but let's not think about that for the moment).

    I want to be able to expose the Service and Operations through the three endpoints, and have a unified authentication model.  My current thinking being:

    UserName/Password authentication for Ajax and SOAP clients since these will be produced internally and will be for use by our standard website users - who have usernames and passwords.
    Certificate-based authentication for the B2B scenario.
    VPN-based security (as in a network location that is only accessible to machines within our AD) for scenario e.

    I want to have a public service that manages authentication in the event that username/password is required (for the web-page scenario etc).  I'd rather not have to rely on cookies for the persistence of this login state in the username/password scenario, since cookies can be hacked - however if this has to be the case then so be it.

    However, there is also a requirement that the transport of some (but not all) service operation data would have to be encrypted - meaning in this case that Https would need to be used.

    Here the problems start.

    Firstly - selectively exposing different operations within the service contracts on endpoints with different settings for Transport-level security is not possible unless you split up the service contracts into separate entities.  This complicates the process managing and growing the service layer in line with the vanilla layer.

    Assuming however, that this limitation is accepted, there are the endpoints themselves:

    • With wsHttp, there are no problems - Transport Security can be used in partnership with client certificates-based authentication since the wsHttp security model is  We can also use client authentication on its own.
    • With BasicHttp (SOAP), Transport security can also be used in partnership with message-based security - from what I can gather.
    • With webHttp (AJAX), it's impossible to have client authentication in tandem with transport-level security; it's either one or the other.
    Is this last point true?  Or is it the case that simply the initial transfer of credentials using Http Basic is not supported over SSL.

    I don't want to be relying on the nasty browser password popups for authentication in the username/password scenario (people should only see them when they have no chance of authenticating) - instead I want to be able to automatically send these credentials (using an HMAC-encrypted password transfer) either in response to a user-friendly login box built into the Client UI - or even to be able to do it automatically from script served up to one of our own web pages (to prevent double-logins on the website itself.

    I've looked at using Custom Username authentication, but at first I couldn't even get it to kick in, let alone let me through.  And then when I did get it to kick in, the credentials were never passed to my custom password authenticater.  I've thought about hanging off the Asp.Net membership provider and deriving my own one - but then I've had a problem figuring out how to send back the HMAC Key to be used by the client to encrypt the password transfer.

    I know this is a long post, but it must be a scenario that has been considered by somebody!  I've looked at the excellent Patterns and Practises stuff on codeplex - but all the scenarios are too narrow in scope to help me - none of them deal with this multi-client approach directly.
    Tuesday, March 10, 2009 9:41 AM


All replies

  • Hi,

    It looks like Claim-Based security with Geneva Framework could work partially. You can basically have a single service (Secure Token Service) to handle any possible authentication scenario (or transport configuration), that service will produce claims containing identity information about the authenticated user. All your services can be configured to receive those claims, so they do not need to authenticate the user again.

    This, however, will not work out of box for Ajax clients since WS-Security and WS-Trust need to be used for negotiating the claims with the STS. 

    Pablo Cibraro -
    Tuesday, March 10, 2009 4:01 PM
  • [after writing this response I realise I've repeated a few points from my first one - but this time with a bit more detail]
    Hi there,

    Thanks for the pointer towards Geneva - it certainly looks cool.

    It's a possible route - apart from the fact that AJAX functionality is not ready to go - and in that respect I think it'd be fair to say that it represents another alternative which doesn't quite fit - but for different reasons.

    I've started developing my own authentication service - which uses a secure HMAC-based password transfer.  The idea I had was to have any client which needs to authenticate with user/pwd combination (so discounting those which will require client certificates) do it with that service, and the securables would double-check subsequent authentication tokens with that service (so, basically, a private STS solution).  The client would also need to periodically contact the authentication service for a replacement authentication token - thus mitigating the risk of replay attacks.

    I've steered away from cookie-based solutions because they don't protect against XSRF attacks - so my solution would require the client code to manually add the authentication data to any request, as a transport or message-level header - that way, it would be harder to initiate bogus requests which piggy-back on the already-authenticated session.

    I then considered implementing the security check on the securable services using a custom UsernamePasswordValidator (wit the password being double-encrypted based on the session and a time-limited unique value) - and using Basic authentication for webHttp and Message-level authentication with Client Credentials for SOAP.  Because the actual data being transferred is already heavily encrypted, it shouldn't be a security risk (any thoughts on this!?).

    In theory, managing the transfer of this token from a SOAP client is easy - but I've had problems with enabling custom username/password authentication on the webHttpBinding - it never actually calls my custom validator.  I've enabled Basic authentication on IIS (and disabled Windows authentication - because the browser defaults to the most secure), and my configuration looks like this:

    1 <system.serviceModel> 
    2         <serviceHostingEnvironment aspNetCompatibilityEnabled="true" /> 
    3         <bindings> 
    4             <webHttpBinding> 
    5                 <binding name="webHttpSecureClientCreds">  
    6                     <security mode="Transport" > 
    7                         <transport clientCredentialType="Basic" realm="proofofconcept" /> 
    8                     </security> 
    9                 </binding> 
    10                 <binding name="webHttpClientCreds">  
    11                     <security mode="TransportCredentialOnly">  
    12                         <transport clientCredentialType="Basic" realm="proofofconcept" /> 
    13                     </security> 
    14                 </binding> 
    15             </webHttpBinding> 
    17         </bindings> 
    18         <services> 
    19             <service behaviorConfiguration="ProtectedServiceBehavior" 
    20              name="ProtectedService">  
    21                 <!-- other endpoints ommitted --> 
    22                 <endpoint address="Ajax" behaviorConfiguration="AjaxBehaviour" binding="webHttpBinding" bindingConfiguration="webHttpClientCreds" contract="IProtectedService" /> 
    23             </service> 
    25         </services> 
    26         <behaviors> 
    27             <endpointBehaviors> 
    28                 <behavior name="JobServe.Eventing.RadiusServer.AjaxBehaviour">  
    29                     <enableWebScript /> 
    30                 </behavior> 
    31             </endpointBehaviors> 
    32             <serviceBehaviors> 
    33                 <behavior name="ProtectedServiceBehavior">  
    34                     <serviceCredentials> 
    35                         <userNameAuthentication userNamePasswordValidationMode="Custom" 
    36                          customUserNamePasswordValidatorType="AuthenticationServiceCredentialValidator, AssemblyName" /> 
    37                     </serviceCredentials> 
    38                 </behavior> 
    39             </serviceBehaviors> 
    40         </behaviors> 
    41     </system.serviceModel> 

    Initially, all I'm doing to test this is to fire the protected url in the browser, and see if I can at least get it to authenticate with a fixed username/password.  But it never even hits the WCF stack!

    I've tried both the Transport and TransportCredentialOnly binding configurations, and neither of them work.

    I've tried wiping the 'Realm' (since it strictly means nothing to my custom auth code), and i've even tried authenticating using my AD credentials since the MSDN topic which describes the UserNamePasswordValidator says:

    When a WCF service is hosted in Internet Information Services (IIS) using transport-level security and the UserNamePasswordValidationMode property is set to Custom, the custom authentication scheme uses a subset of Windows authentication. That is because in this scenario, IIS performs Windows authentication prior to WCF invoking the custom authenticator.

    This is a bit non-descript...  But I've got a horrible feeling that it basically means you can't do what I want to do unless the credentials I'm using are Windows credentials!?

    Is this a complete blind alley?  I'm really considering bypassing WCF security (against my better judgment) completely and implementing the header checks as early as possible somewhere else - perhaps in the Asp.Net pipeline instead.

    Or would forms authentication and the authentication service provide a hassle-free option?

    Any further pointers would be greatly appreciated.

    • Edited by Lord Zoltan Tuesday, March 10, 2009 9:04 PM Realised some things have already been outlined in first post.
    Tuesday, March 10, 2009 9:00 PM
  • ->That is because in this scenario, IIS performs Windows authentication prior to WCF invoking the custom authenticator.

    This is a known issue for IIS, Dominick Baier has implemented a workaround solution which involves writing custom IHttpModule, ISecurityPolicy etc:

    Hope that helps

    Another Paradigm Shift
    • Marked as answer by Lord Zoltan Thursday, March 12, 2009 4:36 PM
    Thursday, March 12, 2009 8:50 AM
  • Ah, now that's more like it!

    I did consider doing something like this, but wondered if IIS would still kick in beforehand...

    Obviously not!

    Excellent - thanks for the pointer.
    Thursday, March 12, 2009 4:36 PM