none
Unable to get SecurityTokenHandler and/or ClaimsAuthenticationManager to execute RRS feed

  • Question

  • I haven't done custom authentication in WCF in a while, plus I'm having difficulty finding up-to-date documentation since .Net 4.5 WCF has integrated WIF, so this question probably has a very obvious answer. My issue is that I have a service that calls another service that requires a couple of credential values passed in for every method call, but I want my service to avoid requiring these same parameters for every method (part of the reason is that the service it calls may get swapped out for one that requires different values). My intent was to include a SecurityToken in the header that stored the values so that the user can call a user/password LogOn method that will put the resulting credentials from the other service on the outgoing message header and stick with subsequent method calls such that they already have the credentials attached.

    However, I've tried using identityModel's <securityTokenHandler/> element to include this token, but even with CanValidate=true, my token is not getting called. Additionally, I've tried using <claimsAuthenticationManager/> and override Authenticate, but that method isn't being called either. My config file is just a basic WCF Service project with an IService1 interface and the aforementioned identityModel elements, and I've also tried changing the service binding from wsHttpBinding to wsHttpContextBinding, but that's not helping.

    I'm sure what I'm forgetting is something that should be obvious, but I've been staring at this problem for so long, I can't see the forest for the trees.

    Thursday, February 13, 2014 7:50 PM

Answers

  • I got it working with help from http://social.msdn.microsoft.com/Forums/vstudio/en-US/ddd45b84-f0d4-48b2-9ce0-1042bea06193/error-while-using-a-custom-username-password-validator-with-geneva?forum=Geneva

    Off course I adjusted the API(s) for system.identitymodel but conceptually the idea was similar.


    Rajneesh

    • Marked as answer by RKPatrick Thursday, March 13, 2014 2:21 PM
    Tuesday, March 11, 2014 2:47 PM
  • Finally have it working! My config code was mostly correct, but I still had my custom STS exposed (it really isn't necessary if I have my custom token handler), which was creating a Scope with no signing credentials. When it called into base.Issue, that led to an error (before the handler was ever invoked). By removing the STS and just using a basic WSTrustContract (see below), everything works OK...all via configuration (no factory et at)

    <services>
        <service name="MyService">
            <endpoint binding="ws2007HttpBinding"
                        contract="IMyService"/>
            <endpoint address="mex"
                        binding="mexHttpBinding"
                        contract="IMetadataExchange"/>
            <host>
                <baseAddresses>
                    <add baseAddress="http://localhost:8001/MyService.svc"/>
                </baseAddresses>
            </host>
        </service>
        <service name="System.ServiceModel.Security.WSTrustServiceContract">
            <endpoint binding="ws2007HttpBinding"
                        contract="System.ServiceModel.Security.IWSTrust13SyncContract"/>
        </service>
    </services>
    <behaviors>
        <serviceBehaviors>               
            <behavior>
                <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
                <serviceDebug includeExceptionDetailInFaults="true"/>
                <serviceCredentials useIdentityConfiguration="true">
                    <serviceCertificate x509FindType="FindBySubjectName" findValue="*********" storeLocation="LocalMachine" storeName="My"/>
                </serviceCredentials>
            </behavior>
        </serviceBehaviors>
    </behaviors>
    <bindings>
        <ws2007HttpBinding>
            <binding maxReceivedMessageSize="20000000">
                <security mode="Message">
                    <message establishSecurityContext="False" clientCredentialType="UserName"/>
                </security>
            </binding>
        </ws2007HttpBinding>

    <system.identityModel>
        <identityConfiguration>
            <securityTokenHandlers>
                <remove type="System.IdentityModel.Tokens.WindowsUserNameSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
                <add type="MyHandler" />
            </securityTokenHandlers>
        </identityConfiguration>
    </system.identityModel>
    

    • Edited by RKPatrick Thursday, March 13, 2014 2:20 PM Added identitymodel config
    • Marked as answer by RKPatrick Thursday, March 13, 2014 2:20 PM
    Wednesday, March 12, 2014 8:20 PM

All replies

  • Hi,

    The standard token handler again authenticates against Windows accounts. When you want to authenticate against your custom credential store, you need to supply a custom security token handler.

    You could configure your token handler in configuration, but you can also do it via code:

    host.Credentials.UseIdentityConfiguration = true;
    var idConfig = new IdentityConfiguration();
    idConfig.SecurityTokenHandlers.AddOrReplace(
    
    new GenericUserNameSecurityTokenHandler((uname, password) => uname == password));
    
    host.Credentials.IdentityConfiguration = idConfig;
    

    …or if you simply want to wrap an existing password validator:

    idConfig.SecurityTokenHandlers.AddOrReplace(
    
        new GenericUserNameSecurityTokenHandler(
    
            (uname, password) => 
    
                {
    
                    try
    
                    {
    
                        var validator = new PasswordValidator();
    
                        validator.Validate(uname, password);
    
                        return true;
    
                    }
    
                    catch (SecurityTokenValidationException)
    
                    {
    
                        return false;
    
                    }
    
                }));
    

    The final step is to tell WCF to put the ClaimsPrincipal coming from the token handler on Thread.CurrentPrincipal using the following service behavior:

    <serviceAuthorizationprincipalPermissionMode=Always/>

    The end result is a ClaimsPrincipal containing the username, authentication method and authentication instant claims. Also the claims transformation/validation/authorization pipeline will be called if configured.

    Best Regards,
    Amy Peng


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Friday, February 14, 2014 2:42 AM
    Moderator
  • You could configure your token handler in configuration, but you can also do it via code:

    How would I do this? I've put the following in my service's config file, but it's not validating (or calling any method) prior to the method executing:

        <system.identityModel>
            <identityConfiguration>
                <securityTokenHandlers>
                    <clear/>
                    <add type="ServiceTest.TestSecurityTokenHandler"/>
                </securityTokenHandlers>
            </identityConfiguration>
        </system.identityModel>
    

    Friday, February 14, 2014 3:30 PM
  • OK, the following allows my token handler to actually initialize:

        <behaviors>
          <serviceBehaviors>
            <behavior>
                <serviceCredentials useIdentityConfiguration="true"/>
    

    HOWEVER, while it will now call GetTokenTypeIdentifiers to execute (since identify infrastructure is now initializing), CanValidateToken isn't called. I've tried naming my identity config and pointing serviceCredentials to that, but it still isn't called.

    I'm going to try to look through disassembled identityModel code and set some deep breakpoints to see if I can track down what condition isn't be met (it's almost getting to seem like it isn't even worth the trouble, though)

    Friday, February 14, 2014 5:16 PM
  • I'm getting the token handler's methods to be called now. I had to add a service certificate to the behavior (and spent a couple of hours strugging with *that*, as the MSDN self-signing certificate example doesn't allow private key exchange). I'm still not where I need to be, though, as the XmlReader that ReadToken is getting serializes to an empty string, and stubbing out that leads to yet another error (I'm in double digits in terms of layers of the error onion) due to DerivedTokenAuthenticator being null.

    After all this effort (two days worth), I *really* hope that I am on the right path in terms of what I am trying - basically all I want to do is have a login method put one or more authentication GUIDs on the subsequent method headers to indicate that the client has logged in and then use those GUIDs to make its own calls to another web service.

    Friday, February 14, 2014 10:42 PM
  • Is GenericUserNameSecurityTokenHandler part of the BCL? I tried searching Bing for it and came up empty.
    Tuesday, February 25, 2014 10:27 PM
  • Hi Keith;

    I'm struggling at the same point as you.. Were you able to solve it?

    Please tell me what should I do to get my token handler's methods to be called..

    GenericUserNameSecurityTokenHandler is not part of BCL but Thinktecture Identity model. I think!!

     

     


    Rajneesh

    Monday, March 10, 2014 5:16 PM
  • No, I never got it working. I tried going down a different path (using an Active STS), but ran into problems there, including no answer to *that* post. At some point (hopefully this week), I'm going to sit down at home with the sample and try it again.
    Monday, March 10, 2014 8:49 PM
  • I got it working with help from http://social.msdn.microsoft.com/Forums/vstudio/en-US/ddd45b84-f0d4-48b2-9ce0-1042bea06193/error-while-using-a-custom-username-password-validator-with-geneva?forum=Geneva

    Off course I adjusted the API(s) for system.identitymodel but conceptually the idea was similar.


    Rajneesh

    • Marked as answer by RKPatrick Thursday, March 13, 2014 2:21 PM
    Tuesday, March 11, 2014 2:47 PM
  • Still can't get it to work on my end. As soon as I add an entry for the custom handler on my test app, I get an error "The token Serializer cannot serialize 'System.IdentityModel.Tokens.SessionSecurityToken'.  If this is a custom type you must supply a custom serializer."  AFAIK, I'm not using a custom type, and none of my handler's methods are being called. I wish the exception message followed up with "If not, ...???"

    I'm assuming the error is a result of <clear/>ing the handler element before adding my own. However, if I don't clear it, I get an error due to the key already existing in the collection (even though there isn't a key). The other thing that is confusing me is where SessionSecurityToken is even coming from. I know it's coming out of a token cache, but why? (I don't have sessions enabled)

    • Edited by RKPatrick Tuesday, March 11, 2014 8:33 PM asdf
    Tuesday, March 11, 2014 6:50 PM
  • Here's the deal with the serialization error - <clear/>ing the token handler collection is a bad idea. Maybe it worked in the MS.IdentityModel days (which seems to be the only stuff Bing returns, unfortunately), but there are about 10 different handlers in 4.5. But taking out the clear results in a duplicate key error because of the WindowsUserName handler already dealing with UserName token types. So what you've got to do is just remove that one:

    <system.identityModel>
        <identityConfiguration>
            <securityTokenHandlers>
                <!-- Wants the full name, including public key! -->
                <remove type="System.IdentityModel.Tokens.WindowsUserNameSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
                <add type="FederationTest.Identity.TokenHandler, FederationTest" />
            </securityTokenHandlers>
        </identityConfiguration>
    </system.identityModel>
    

    I'm still not running, as I carved up my config file so much, I'm getting CardSpace errors on top of stuff about no endpoint for "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue" (on top of a LOT of IISExpress crashes - I swear this problem does not want to be solved)

    Tuesday, March 11, 2014 10:18 PM
  • I was doing the same thing but the following in my servicehostfactory did the trick

     
     public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
            {
                SecurityTokenServiceConfiguration config = new SecurityTokenServiceConfiguration("xyz");
    
                config.SecurityTokenService = typeof(RPServiceSTS);
                config.AudienceRestriction.AudienceMode = AudienceUriMode.Never;
    
                WSTrustServiceHost host = new WSTrustServiceHost(config, baseAddresses);
    
                foreach (var item in SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection())
                {
                    config.SecurityTokenHandlers.AddOrReplace(item);
                }
    
                config.SecurityTokenHandlers.AddOrReplace(new GenericUserNameSecurityTokenHandler());
                Uri baseUri = baseAddresses.FirstOrDefault(a => a.Scheme == Uri.UriSchemeHttp);
                
                host.AddServiceEndpoint(typeof(IWSTrust13SyncContract), GetWindowsCredentialsBinding(), baseUri.AbsoluteUri);
                return host;
            }
            static Binding GetWindowsCredentialsBinding()
            {
                WS2007HttpBinding binding = new WS2007HttpBinding();
                binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
                binding.Security.Message.EstablishSecurityContext = false;
                binding.Security.Mode = SecurityMode.Message;
    
                return binding;
            }

     


    Rajneesh

    Wednesday, March 12, 2014 12:15 PM
  • Could you show me your server-side config file? I've been doing most of the same stuff in the config, but when I pasted your factory code into mine, I started getting CardSpace errors due to the issuer binding not being set on my client.
    Wednesday, March 12, 2014 6:41 PM
  • Finally have it working! My config code was mostly correct, but I still had my custom STS exposed (it really isn't necessary if I have my custom token handler), which was creating a Scope with no signing credentials. When it called into base.Issue, that led to an error (before the handler was ever invoked). By removing the STS and just using a basic WSTrustContract (see below), everything works OK...all via configuration (no factory et at)

    <services>
        <service name="MyService">
            <endpoint binding="ws2007HttpBinding"
                        contract="IMyService"/>
            <endpoint address="mex"
                        binding="mexHttpBinding"
                        contract="IMetadataExchange"/>
            <host>
                <baseAddresses>
                    <add baseAddress="http://localhost:8001/MyService.svc"/>
                </baseAddresses>
            </host>
        </service>
        <service name="System.ServiceModel.Security.WSTrustServiceContract">
            <endpoint binding="ws2007HttpBinding"
                        contract="System.ServiceModel.Security.IWSTrust13SyncContract"/>
        </service>
    </services>
    <behaviors>
        <serviceBehaviors>               
            <behavior>
                <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
                <serviceDebug includeExceptionDetailInFaults="true"/>
                <serviceCredentials useIdentityConfiguration="true">
                    <serviceCertificate x509FindType="FindBySubjectName" findValue="*********" storeLocation="LocalMachine" storeName="My"/>
                </serviceCredentials>
            </behavior>
        </serviceBehaviors>
    </behaviors>
    <bindings>
        <ws2007HttpBinding>
            <binding maxReceivedMessageSize="20000000">
                <security mode="Message">
                    <message establishSecurityContext="False" clientCredentialType="UserName"/>
                </security>
            </binding>
        </ws2007HttpBinding>

    <system.identityModel>
        <identityConfiguration>
            <securityTokenHandlers>
                <remove type="System.IdentityModel.Tokens.WindowsUserNameSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
                <add type="MyHandler" />
            </securityTokenHandlers>
        </identityConfiguration>
    </system.identityModel>
    

    • Edited by RKPatrick Thursday, March 13, 2014 2:20 PM Added identitymodel config
    • Marked as answer by RKPatrick Thursday, March 13, 2014 2:20 PM
    Wednesday, March 12, 2014 8:20 PM