locked
Error while using a custom username password validator with Geneva

    Question

  • I've a sample Geneva project, with ClientCredentialType = MessageCredentialType.UserName, where I'm using a custom username password validator. However:
    1. The custom validator never gets called.
    2. The service throws an exception when validating the token because it tries to use the windows store to validade the (username,password) pair.

    I've looked into the Microsoft.Identity.dll and found the following:

    1. The exception is being thrown inside the ValidateToken method of the WindowsUserNameSecurityTokenHandler class.
    2. This class derives from the abstract UserNameSecurityTokenHandler class. However, this class has only two derived classes:
      1. WindowsUserNameSecurityTokenHandler
      2. MembershipUserNameSecurityTokenHandler
    3. I was unable to locate a username token handler that uses a custom username validator

    Am I missing something here? Are custom username validators still supported?

    Thanks
    Pedro Félix

    Wednesday, November 12, 2008 11:43 AM

Answers

  • Geneva takes over parts of WCF security, e.g. certificate validation and password validation. As a result a number of WCF config knobs don't have effect anymore, e.g. password validators, known certificates in federation and certificate validation. See e.g. here:

    http://www.leastprivilege.com/GenevaIsTheNewWCFSecurity.aspx

    This is also mentioned in the changes from Zermatt to Geneva document on connect.

    Regarding password validation - by default username/passwords are checked against Windows accounts. You can either use the membership handler or provide your own as Pedro mentions.

    I guess this will lead to even more confusion as we move on - but on the other hand i think these changes/additions are to the better.

    HTH
    Dominick Baier - http://www.leastprivilege.com
    • Marked as answer by Pedro Felix Sunday, November 16, 2008 2:14 PM
    Thursday, November 13, 2008 5:35 PM
  • I was able to get around this by replacing the SecurityTokenHandler for the WindowsUserNameSecurityTokenHandler by making a call to the SecurityTokenServiceConfiguration.SecurityTokenHandlers.AddOrReplace method.

    config.SecurityTokenHandlers.AddOrReplace(new MembershipUserNameSecurityTokenHandler(new MyMemberShipProvider()));

    It's not spectacular, but it does let you get around this issue via a MembershipProvider. I'm not sure about the Custom Username validator though.

    Thomas
    • Edited by neotom Wednesday, November 12, 2008 6:14 PM
    • Proposed as answer by neotom Wednesday, November 12, 2008 8:44 PM
    • Marked as answer by Pedro Felix Sunday, November 16, 2008 2:13 PM
    Wednesday, November 12, 2008 6:14 PM

All replies

  • I was able to get around this by replacing the SecurityTokenHandler for the WindowsUserNameSecurityTokenHandler by making a call to the SecurityTokenServiceConfiguration.SecurityTokenHandlers.AddOrReplace method.

    config.SecurityTokenHandlers.AddOrReplace(new MembershipUserNameSecurityTokenHandler(new MyMemberShipProvider()));

    It's not spectacular, but it does let you get around this issue via a MembershipProvider. I'm not sure about the Custom Username validator though.

    Thomas
    • Edited by neotom Wednesday, November 12, 2008 6:14 PM
    • Proposed as answer by neotom Wednesday, November 12, 2008 8:44 PM
    • Marked as answer by Pedro Felix Sunday, November 16, 2008 2:13 PM
    Wednesday, November 12, 2008 6:14 PM
  • First of all, thanks for the reply.
    Yes, I think that the solution you've proposed will work. I can also create a custom SecurityTokenHandler that uses the registered custom validator to validade the username+password. However, it seems strange that this scenario is not supported "out-of-the-box". Is this by "design"?

    Thanks
    Pedro Félix
    Wednesday, November 12, 2008 8:04 PM
  • I'm not entirely sure. Also, I tried to create a custom username security token handler and work with it that way, but it gets fissy due to a lack of a custom principal specified in the authorization context.
    Thomas
    Wednesday, November 12, 2008 8:43 PM
  • I can't get UserName authentication to work for the STS either. 
    It all worked before I installed the last bits, but now I seem to be hitting a brick wall all over again.


    The scenario using windows authenticaiton works all right; I then change the bindings on both ends to use UserName mode, set a service certificate (and skipped validation, as it is a dummy one) and added my custom username authorisation, but, like you, when I run this I get an error on the STS' trace saying 
    "ID4063: LogonUser failed for the 'Admin' user. Ensure that the user has a valid Windows account." (admin being the username I'm providing through the proxy)

    Same configuration worked a couple of weeks back (on "Zermatt")



    Yossi Dahan
    Thursday, November 13, 2008 11:43 AM
  • Hi Yossi,

    I've done a little analysis of the Geneva code, and it appears that there isn't "out-of-the-box" support for custom password validators in Geneva Beta 1. I don't know it this is "by design" or something that is just lacking in beta 1.

    I will see if I can create a workaround by defining a custom  token handler.

    Regards
    Pedro Félix
    Thursday, November 13, 2008 12:23 PM
  • Thanks for the update Pedro, again this is odd as it definitely worked previously.
    Let me know if you find a workaround, I think I'll go down the Asp.net membership route for now.


    Yossi Dahan
    Thursday, November 13, 2008 12:25 PM
  • The custom SecurityTokenHandler solution worked, at least in my testing environment. Here it goes:

    1. Create a custom UserNameSecurityTokenHandler handler. The one below derives from WindowsUserNameSecurityTokenHandler for simplicity reasons
    1 class CustomValidatorUserNameSecurityTokenHandler : WindowsUserNameSecurityTokenHandler {  
    2  
    3         ServiceHostBase sh;  
    4  
    5         public CustomValidatorUserNameSecurityTokenHandler(ServiceHostBase sh) {  
    6             this.sh = sh;  
    7         }  
    8  
    9         public override ClaimsIdentityCollection ValidateToken(SecurityToken token) {  
    10             if (sh.Credentials.UserNameAuthentication.UserNamePasswordValidationMode == UserNamePasswordValidationMode.Windows) {  
    11                 return base.ValidateToken(token);  
    12             } else if (sh.Credentials.UserNameAuthentication.UserNamePasswordValidationMode   
    13                     == UserNamePasswordValidationMode.MembershipProvider) {  
    14                 // TODO add support for membership provider here  
    15                 throw  new SecurityTokenException("Unable to validade username and passwords");  
    16             }else{  
    17                 if (token == null) {  
    18                     throw new ArgumentNullException();  
    19                 }  
    20                 UserNameSecurityToken untoken = token as UserNameSecurityToken;  
    21                 if(untoken == null){  
    22                     throw new SecurityTokenException("Invalid token");  
    23                 }  
    24  
    25                 sh.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator.Validate(untoken.UserName,untoken.Password);  
    26                 // If the thread arrives here, then the (username,password) pair is valid  
    27                   
    28                 // The following code is based on the "reflection" of the class MembershipUserNameSecurityTokenHandler  
    29                 Claim nameClaim = new Claim(System.IdentityModel.Claims.ClaimTypes.Name, untoken.UserName);  
    30                 IClaimsIdentity ident = new ClaimsIdentity(nameClaim,"Password");  
    31                 ident.Claims.Add(  
    32                     new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant",   
    33                         XmlConvert.ToString(DateTime.UtcNow, "yyyy-MM-ddTHH:mm:ss.fffZ"), "http://www.w3.org/2001/XMLSchema#dateTime")  
    34                 );  
    35                 ident.Claims.Add(  
    36                     new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod",   
    37                         "http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password")  
    38                 );  
    39                 return new ClaimsIdentityCollection(new IClaimsIdentity[] { ident });  
    40             }  
    41         }  
    42     } 

    2. Give an instance of this handler to the FederatedServiceCredentials.ConfigureServiceHost method, as showed below. Notice that this instance must receive the ServiceHost reference in the ctor. This is needed so that the handler can access the validator.

    ServiceHost sh = new ServiceHost(...);  
     
      var sthc = new SecurityTokenHandlerCollection{  
        new CustomValidatorUserNameSecurityTokenHandler(sh),  
      };  
     
    FederatedServiceCredentials.ConfigureServiceHost(sh, sthc, new SimpleIssuerNameRegistry  
                                                         (),TimeSpan.FromMinutes(5)); 

    HTH
    Pedro Felix

    Thursday, November 13, 2008 2:27 PM
  • Nice on Pedro....but I must be still missing something - as soon as I add my handler I get the following expcetion, on the RP side I believe - 

    "The token Serializer cannot serialize 'Microsoft.IdentityModel.Tokens.EncryptedSecurityToken'.  If this is a custom type you must supply a custom serializer."

    I have this in my factory - 

                SecurityTokenHandlerCollection handlers = new SecurityTokenHandlerCollection { new MyUserNameSecurityTokenHandler(host) };
                FederatedServiceCredentials.ConfigureServiceHost(host, handlers);

    MyUserNameSecurityTokenHandler is pretty much the code you've posted.

    You said this worked for you? any idea? 

    Yossi Dahan
    Thursday, November 13, 2008 4:11 PM
  • Nice work Pedro, however to your point, I will like to find out why this is not supported OOB as it was in Zermatt. Anyone from MS care to comment?????
    Thursday, November 13, 2008 4:46 PM
  • Geneva takes over parts of WCF security, e.g. certificate validation and password validation. As a result a number of WCF config knobs don't have effect anymore, e.g. password validators, known certificates in federation and certificate validation. See e.g. here:

    http://www.leastprivilege.com/GenevaIsTheNewWCFSecurity.aspx

    This is also mentioned in the changes from Zermatt to Geneva document on connect.

    Regarding password validation - by default username/passwords are checked against Windows accounts. You can either use the membership handler or provide your own as Pedro mentions.

    I guess this will lead to even more confusion as we move on - but on the other hand i think these changes/additions are to the better.

    HTH
    Dominick Baier - http://www.leastprivilege.com
    • Marked as answer by Pedro Felix Sunday, November 16, 2008 2:14 PM
    Thursday, November 13, 2008 5:35 PM
  • I agree that some changes, for example the IssuerNameRegistry are very good, but I have to say I think that 

    1) Username/password authentication is a very common scneario I believe, and I found the custom validators approach in WCF so straight forward I'm really surprised about the choice to remove it. I understand it can be achieved using Pedro's approach or a custom membership provider, but why remove a good thing?

    2) Having WCF configuration ignored, or treated differently will make something already fairly complex (as WCF configuration can often be) worse; if this is the case I think validation should be done at runtime to warn/error when configuration is not align with Geneva

    3) I must have missed that bit in the changes doc, and I have just spent the good part of 3 days to get this to work before learning (thanks again Pedro) that this is not supported. again - this just highlights the confusion that this will cause between WCF and Genva.

    I hope these make sense (and unfortunatley for me I still can't get either approaches to work....but I may need to take a step back from it for a day or two...)

    Yossi Dahan
    Thursday, November 13, 2008 5:47 PM
  • Hello again Yossy,

    Yes, the code I've showed did work on my test machine. I'm using a straight WSHttpBinding with ClientCredentialType = MessageCredentialType.UserName.

    1) Are you using a federation (STS) scenario?

    2) You can try to change the ValidateToken method, so that it just calls base.ValidateToken(). Then, the original error should happen. See also if the ValidateToken gets called.

    HTH
    Pedro Felix
    Thursday, November 13, 2008 6:08 PM
  • Yep - I agree that common extensibility points should be easier to reach...


    Dominick Baier - http://www.leastprivilege.com
    Thursday, November 13, 2008 6:30 PM
  • hanks Pedro, 

    Yes I am using a federation scenario. 

    I have an active STS I've created (nothing too clever at this point)
    and then a "claims aware" web service and a test winForms client. 

    As long as I leave the STS to use ws2007HttpBinding with MessageCredentialType.Windows everything works fine.
    What I need is to change it to use a custom username/password validation (I actually need a sample using the Asp.net membership tables, but one thing at a time :-) )

    as I've said I had this working on "Zermatt", but now I've followed your lead.

    I'm trying to avoid posting all the configurations as this can get quite big (maybe we should take this offline?), but generally all I did is changed the message security element on both the STS and the client to clientCredentialType="UserName"

    on the STS behaviour I added a serivcecredentials element with a service certificate and usernameAuthentication.
    and, after your post I've added your code to my factory:

                SecurityTokenHandlerCollection handlers = new SecurityTokenHandlerCollection { new HRGUserNameSecurityTokenHandler(host) };
                FederatedServiceCredentials.ConfigureServiceHost(host, handlers);

    I have not changed anything in the RP WS (wouldn't expect to need to, as it shouldn't care how I'm authenticating with the STS) and on the client side, apart from setting the message security to UserName I only make sure I provide username and password through the proxy.

    and I'm getting the error mentioned above - 
    "The token Serializer cannot serialize 'Microsoft.IdentityModel.Tokens.EncryptedSecurityToken'.  If this is a custom type you must supply a custom serializer."

    any ideas? (if you prefer you can contact me on my email and I'll post whatever we find here later - yossi.dahan at sabratech co uk)


    Forgot to add - the error I'm getting is happening as soon as I register the handler with the host, regardles of whether I'm using Windows or UserName modes)

    Yossi Dahan
    Thursday, November 13, 2008 6:44 PM
  •  Maybe this helps:
    http://www.leastprivilege.com/UsernamePasswordValidationWithGeneva.aspx


    Dominick Baier - http://www.leastprivilege.com
    Thursday, November 13, 2008 6:55 PM
  • Hi Dominick,

    1) Regarding my solution: I'm adding the token handler programmatically because I needed to access the Service Host (in order to access the registered custom validator). Do you know any way of accessing the service host inside the token handler, without passing it in the token handler ctor?

    2) Regarding WindowsUserNameSecurityTokenHandler and MembershipUserNameSecurityTokenHandler: It appears that the CreateSecurityTokenAuthenticator method, of the FederatedSecurityTokenManager class, selects the token handler solely based on the token type and not on the token validation properties configured in the service host. So, if we have multiple registered token handlers for the same token type (e. g. WindowsUserNameSecurityTokenHandler and MembershipUserNameSecurityTokenHandler), we have no assurance that the correct one will be chosen.

    Regards
    Pedro Felix
    Thursday, November 13, 2008 7:27 PM
  • I've just found out if a SecurityTokenHandlerCollection is defined explicitly, then the default handlers are not considered, namely the EncryptedSecurityTokenHandler.

    HTH
    Pedro
    Friday, November 14, 2008 2:46 PM

  • Thanks for all the help, online and offline;

    As you've suggested I had to make sure I "AddOrReplace" the new handler to the collection of handlers already configured.
    otherwise I would have removed the Saml handlers, whcih results in the error I was getting when the STS tries to issue the token -

    ID4010: A SecurityTokenHandler is not registered for token type ''

    After making sure the handler is added to the existing list of handlers I could get the scenario to work in a managed host.
            static void Main(string[] args) 
            { 
                // Create and setup the configuration for our STS 
                MySTSConfiguration config = new MySTSConfiguration(); 
     
                // Create the WS-Trust service host with our STS configuration 
                using (WSTrustServiceHost host = new WSTrustServiceHost(config)) 
                { 
                    config.SecurityTokenHandlers.AddOrReplace(new MyUserNameSecurityTokenHandler(host)); 
                    host.Open(); 
                    Console.WriteLine("STS started, press ENTER to stop ..."); 
                    Console.ReadLine(); 
                    host.Close(); 
                } 
            } 
     
     


    The problem is now to get it to work in IIS

    Currently I have the factory in which I have access to the host, and the configuration class, in which I have access to the collection of security handlers, but I don't have access to both in either as far as I can tell; so, considering, as you've pointed out, the custom handler needs the host to figure out the authentication mode, I have a bit of a catch 22.




    Yossi Dahan
    Friday, November 14, 2008 2:52 PM

  • ok. Thanks to Pedro's help I've now figured out the last piece on how to get this working hosted in IIS. 

    I needed to do some more work in the factory, here's how mine looks like now - 


        public class MySTSServiceFactory : WSTrustServiceHostFactory 
        { 
            public override System.ServiceModel.ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses) 
            { 
                MySTSConfiguration config = new MySTSConfiguration(); 
                WSTrustServiceHost host = new WSTrustServiceHost(config,baseAddresses); 
                foreach (SecurityTokenHandler handler in SecurityTokenHandlerCollection.DefaultHandlers) 
                    config.SecurityTokenHandlers.AddOrReplace(handler); 
                config.SecurityTokenHandlers.AddOrReplace(new MyUserNameSecurityTokenHandler(host)); 
                //FederatedServiceCredentials.ConfigureServiceHost(host); 
                return host; 
            } 
        } 
     


    Basically the factory created the configuration explicitly (you can use the constructor string, I didn't.
    I  then create the service WSTrustServiceHost passing in the config I've created.
    In my config the SecurityTokenHandlers list is now empty, so I copy in all the default ones (arguably this can be done in the config constructor)
    I then replace the builtint WindowsUserNameSecurityTokenHandler handler with my custom username handler, this handles all modes (username/password, windows (through base class) and membership).
    I can then return the host.

    Important to note (because I missed it initially) that I hav ecommented out the ConifgureServiceHost which I had from my initial code. this of course, overrides all the changes made through the config.

    In the STS configuration I have set up the endpoint to Message security mode with cclientCredentialType as UserName  and a userNameAuthentication service credentials as before. the handler picks up this setting to initialise the type for authentication.

    Couldn't have gotten here without Pedro's and Dominic's help - thanks guys!

    I have to say this seems too much work for something that used to work straight out of the box! (well, pretty much)

    Yossi Dahan
    Friday, November 14, 2008 3:50 PM
  • public class UserNameSecurityTokenHandler : Microsoft.IdentityModel.Tokens.UserNameSecurityTokenHandler 
        { 
            WindowsUserNameSecurityTokenHandler windowsHandler; 
            MembershipUserNameSecurityTokenHandler membersHandler; 
            UserNamePasswordValidationMode validationMode; 
            UserNamePasswordValidator validator; 
     
            public UserNamePasswordValidationMode ValidationMode 
            { 
                get { return validationMode; } 
                set { validationMode = value; } 
            } 
     
            public UserNamePasswordValidator Validator 
            { 
                get { return validator; } 
                set { validator = value; } 
            } 
     
            public UserNameSecurityTokenHandler(UserNamePasswordValidationMode validationMode,  
                UserNamePasswordValidator validator) : this(validationMode) 
            { 
                this.validator = validator; 
            } 
     
            public UserNameSecurityTokenHandler(UserNamePasswordValidationMode validationMode) 
            { 
                this.validationMode = validationMode; 
            } 
     
            public UserNameSecurityTokenHandler() 
            { 
                this.validationMode = UserNamePasswordValidationMode.Windows; 
            } 

            public override bool CanValidateToken
            {
                get
                {
                    return true;
                }
            }
     
            public override ClaimsIdentityCollection ValidateToken(SecurityToken token) 
            { 
                if (validationMode == UserNamePasswordValidationMode.Windows) 
                { 
                    // Use the windows username security token handler set in place 
                    if (windowsHandler == null
                        windowsHandler = new WindowsUserNameSecurityTokenHandler(); 
                    return windowsHandler.ValidateToken(token); 
                } 
                else if (validationMode == UserNamePasswordValidationMode.MembershipProvider) 
                { 
                    // Simply use the membership provider set in place 
                    if (membersHandler == null
                        membersHandler = new MembershipUserNameSecurityTokenHandler(Membership.Provider); 
                    return membersHandler.ValidateToken(token); 
                } 
                else 
                { 
                    UserNameSecurityToken userNameToken = token as UserNameSecurityToken; 
     
                    if (token == null
                        throw new ArgumentNullException("Invalid token"); 
     
                    // Authenticate the credentials passed in with the security token 
                    validator.Validate(userNameToken.UserName, userNameToken.Password); 
     
                    // Construct the identity used for the custom validation type 
                    Claim nameClaim = new Claim(System.IdentityModel.Claims.ClaimTypes.Name, userNameToken.UserName); 
                    IClaimsIdentity ident = new ClaimsIdentity(nameClaim, "password"); 
                    ident.Claims.Add(new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant",  
                            XmlConvert.ToString(DateTime.UtcNow, "yyyy-MM-ddTHH:mm:ss:fffZ"),  
                            "http://www.w3.org/2001/XMLSchema#dateTime")); 
                    ident.Claims.Add(new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod"
                        "http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password")); 
                    return new ClaimsIdentityCollection(new IClaimsIdentity[] { ident }); 
                } 
            } 
        } 
    Pedro, I've tweeked your UserNameSecurityTokenHandler to support all three UserName types.


    Thomas
    • Edited by neotom Friday, November 21, 2008 7:40 PM
    Sunday, November 16, 2008 2:17 AM

  • Nice Thomas, but looking at this - it is appears it can be even simpler - 

    Pedro inherited his handler from WindowsUserNameSecurityTokenHandler, which is why it was called for all types of validation.

    I could follow your example and  inherit from the abstract class UserNameSecurityTokenHandler, which meant I no longer receive calls if authentication mode was not Custom (MemberShip provider requests were handlerd directly by the standard handlers, without going through my code).

    That means that all the code here is not actually required if all you need to perform is basic custom authentication;
    I've managed to get away with simply creating a class, inheriting from UserNameSecurityTokenHandler and having no implementation whatsoever.
    The custom validator type configured was called to validate the username/password, and then the STS code was executed.

    Of course - if there's more work you wish to do you could, but for me that was enough at this stage.
    The STS logic will then add all the claims I need based on the username.

    this also means that adding this custom handler is much simpler as well as it no longer has parameterised constructor, so I can do it all in the configuration and not in the factory as I had before:

     
    <microsoft.identityModel> 
        <securityTokenHandlers> 
          <remove type="Microsoft.IdentityModel.Tokens.WindowsUserNameSecurityTokenHandler, Microsoft.IdentityModel,Version=0.5.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> 
          <add type="HRG.Profile.Identity.HRGUserNameSecurityTokenHandler, HRG.Profile.Identity.HRGSTS"/> 
        </securityTokenHandlers> 
      </microsoft.identityModel> 
     
     



    I'm still wondering why this extra custom class is required; even more so now that it has no code...
    I wish someone from MS could provide some insight?

    Yossi Dahan
    Monday, November 17, 2008 11:02 AM
  • It's true, I'm hoping this is fixed in the next beta release of Geneva.
    Thomas
    Monday, November 17, 2008 8:38 PM
  • I must confess that the new solution proposed by Yossi, based on an "empty" token handler, puzzled me.

    One possible explanation for this behavior is:
    • The base SecurityTokenHandler class has a property called CanValidateToken, with an implementation returning false.
    • The UserNameSecurityTokenHandler class does not redefine this method, so the registred "empty" token handler will return false when asked if CanValidateToken.
    • The FederatedSecurityTokenManager, when asked for a token authenticator (CreateSecurityTokenAuthenticator method) will locate the handler that handles the given token and then will check if the CanValidateToken property is true. If not, it will call the base CreateSecurityTokenAuthenticator that returns a regular WCF (aware of custom validotors).
    • So, concluding, it appears that with Yossi's solution, a regular WCF authenticator is being used.

    However, I've tried this solution on the RP side (not on the STS side, as Yossi did), using a regular WSHttpBinding, and an exception ocurred in the RP side: "No custom principal is specified in the authorization context". This exception is thrown because the Geneva model requires a "Principal" property in the authorization context. Normally, this property is set by an AuthenticationPolicy that wraps the identity returned by the token handler. However, I've analised the GetAuthorizationPolicies, from the IdentityModelServiceAuthorizationManager, and it appears that this method returns an AuthenticationPolicy that sets the "Principal" property.

    Could someone help shed some light on these issues?

    begin postscript:

    I've found that the exception "No custom principal is specified in the authorization context" only happens at the service side if the establishment of a security context is enabled (which is by default). When there is no security context, then everything runs well.
    I think I can relate this to the code in Microsoft.Identity.dll. However,I must say that I found this behavior extremely odd.
     
    Regarding Yossi's solution: just removing the username handler without adding a new one, produces the same effect.

    end postcript

    Regards
    Pedro Felix


    pfelix.wordpress.com
    Tuesday, November 18, 2008 10:58 AM
  • Took me some time to parse through this entire thread. I will try to address all the questions posed. The "possible explanation" in Pedro's last post accurately captures what is happening behind the scenes. Geneva Framework notices that the custom token handler is unable to validate, so we fallback to WCF behavior.

    Dominick's posts correctly capture how we anticipate people to write custom username validators by using a custom security token handler. One question I have back for the community is:

    Is it significantly more difficult to use a custom username token handler with Geneva versus a custom username validator from WCF? What are the pain points?

    And some observations:
    1. Using a custom token handler and the Geneva Framework fallback to invoke WCF config sounds like a hack which may not work in the next release. It is a goal for the framework to completely ignore WCF config settings around authentication. Thanks for catching this :)
    2. Pedro's point around 'No custom principal': Such errors typically occur when the framework is unable to set Thread.CurrentPrincipal because either the correct TokenHandler.ValidateToken() was not called, or the IdentityModelAuthorizationManager which functions as a catch-all did not kick in. In this case, it sounds like a consistency bug since the exception is thrown only when sessions are turned on.

    This thread is great feedback and exactly what we are looking for -- please keep the comments coming!



    Friday, November 21, 2008 2:36 AM
    Moderator
  • Thanks Shiung....

    I've been following on this, and I can see your point; I might be a bit off the track here, but here's what I think - 

    1. As I've posted before building on top of WCF but ignoring parts of it is confusing, especially if it's not clear what's going on; I would have preferred to be able to do more if I wanted/needed to, but be able to stick to what I know otherwise; if I need a simple custom validator, or to use the membership provider - what's wrong with me using what I know from WCF?
    2. If you do have to go down the route of not supporting WCF configuration - this should be checked and raised in compilation - at least as a warning (and, as you've suggested, you should not fall back the WCF logic)
    3. Last if asked yesterday what are  two pain points around this implementation I would sayd that they are - 
      1.  having to add the custom handler in the factory (as it requires information about the host configuration, such as configuration mode)  and
      2. having to support in my code all methods of authentication

        But following your comment I get the impression that all of these should be ignored and that I can saftely assume that if my handler was called it's because the service should only use this one custom authentication method....

    just my 2 cents

    Yossi Dahan
    Friday, November 21, 2008 3:23 PM
  • 1. One of the goals of the Geneva Framework is to move to a token-handler centric model where anything related to token creation, serialization, and authentication is controlled by the token handler class. Having WCF authentication knobs which change token handler behavior, yet live outside of the token handlers, is not a route we wanted to take, for several reasons:
        a. This breaks the encapsulation model and requires customers who do not know WCF to ramp up on the relatively complex WCF authentication model.
        b. There is no way to think about using token handlers in isolation -- how do you call TokenHandler.ValidateToken() without a WCF context if you depend on WCF knobs? This is particularly important for the ASP.NET case, where WCF is not in the picture yet we need to validate SAML tokens.

    If it helps, the general guidance is to think of Geneva Framework as taking control of security header processing, and any knobs related to authentication should have no effect.

    2. This is a good point which has been raised internally before. We are still looking at how to better alert the end user when they are inadvertently tweaking knobs that we know will have no effect.

    3a. Have you considered adding the custom handler in configuration? If this does not work, what can we do to make it easier?
    3b. Can you elaborate on what you mean by 'all methods of authentication'? Are you referring to the username validation modes (windows, membership, custom), or are you also referring to authentication types (username, kerberos, x509, etc)?

    The idea is you should plugin one user name token handler: WindowsUserNameSecurityTokenHandler, MembershipUserNameSecurityTokenHandler, or your custom derived version of UserNameSecurityTokenHandler for custom validation, depending on how you want to validate usernames and passwords.
    Friday, November 21, 2008 6:57 PM
    Moderator
  • I don't think I understand why WCF configurations are going to be ignored. Not in all cases right? Especially when you setup your service endpoints. I like being able to setup my service endpoint configurations and determine the bindings.

        <services>
          <!-- Setup the STS Service Contract -->
          <service name="Microsoft.IdentityModel.Protocols.WSTrust.WSTrustServiceContract" 
                   behaviorConfiguration="WSTrustBehavior">
            <!-- Setup the base address to host the service -->
            <host>
              <baseAddresses>
                <add baseAddress="http://localhost:6878/BrightShadow/ActiveSTS"/>
              </baseAddresses>
            </host>
            <!-- Setup how clients will access the service -->
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
            <endpoint address="username" binding="ws2007HttpBinding" bindingConfiguration="usernameBinding"
                      contract="Microsoft.IdentityModel.Protocols.WSTrust.IWSTrust13SyncContract" />
          </service>
        </services>
        <bindings>
          <ws2007HttpBinding>
            <binding name="usernameBinding">
              <security mode="Message">
                <message clientCredentialType="UserName" establishSecurityContext="false" />
              </security>
            </binding>
          </ws2007HttpBinding>
        </bindings>
        <behaviors>
          <serviceBehaviors>
            <behavior name="WSTrustBehavior">
              <serviceCredentials>
                <userNameAuthentication userNamePasswordValidationMode="Custom"                                    customUserNamePasswordValidatorType="BrightShadow.TestScenarios.UserName.Config.ActiveSTS.UsernameValidator, BrightShadow.ActiveSTS(user-config)"/>
                <serviceCertificate storeLocation="LocalMachine" storeName="My"
                                    x509FindType="FindBySubjectName" findValue="brightshadow.ssl"/>
              </serviceCredentials>
              <serviceMetadata httpGetEnabled="true"/>
            </behavior>
          </serviceBehaviors>
        </behaviors>

    I think I can understand being able to handle different tokens in passive/active scenarios. But how will WCF clients know what credentials they need if these configurations are ignored?

    post-script
    I modified my custom username security token handler, and added the following. All my code is executed without falling back to the WCF validators but still be able to use my configurations I specified.

            public override bool CanValidateToken
            {
                get
                {
                    return true;
                }
            }

    end post-script


    Thomas
    Friday, November 21, 2008 7:37 PM
  • I should clarify.
     
    Not all of WCF configuration will be ignored. Only authentication-specific settings, examples (also called out in the documentation under Design Considerations):

    1. ServiceHost.Credentials.IssuedTokenAuthentication
    2. ServiceHost.Credentials.ClientCertificate.Authentication
    3. ServiceHost.Credentials.UserNameAuthentication

     

    Bindings, service endpoints, and other WCF configuration not related to authentication are still supported and will continue to work. Note also that these are only on the service side. Geneva Framework does not modify WCF client behavior.

    Friday, November 21, 2008 9:42 PM
    Moderator
  • Thanks for the explanation, it really helps understanding the direction. 
    I can see what you are saying in point #1 (and still think #2 is important...)

    3a I have now added the handler in configuration, and it is easy. my problem up until now was that I the handler I had (based on Pedo's sample) intended to look at the WCF configuration and based on it invoke the relevant validation mechanism (so - basically hooking the handler back to the WCF approach) - this was made difficult because in thehandler I do not have access to the configuration(?) and so I need to somehome pass it into the handler. again - following Pedro's sample this was done through the constructor which meant the handler could no longer be added through configuration. after reading your explanation I have changed my handler to be a lot more specific - it only supports my specific scenario (which makes sense to me now) and I assume different handlers will be used for the different scenarios; this means that no custom constructor is required now and I could move back to using the configuation to add my handler. much nicer.
    to make things better in the handler, access to the configuration would help.

    3b check out the sample Pedro posted earlier in this thread - it checks the WCF authentication settings and invokes the relevant authentication method, all in one handler ( I was refering to Windows, Membership, Custom).

    Again - thanks for the help....

    Now I'm back to my other struggles.....but that's on separate threads...(hint hint)

    Yossi Dahan
    Monday, November 24, 2008 10:10 AM
  •  Thanks guys, this was really helpful!

    I am wondering if there is any insight on:

    3b check out the sample Pedro posted earlier in this thread - it checks the WCF authentication settings and invokes the relevant authentication method, all in one handler ( I was refering to Windows, Membership, Custom).  by Yossi

     

    Also, if I was going to allow two different ways of authentication, InfoCard and CustomUserName/Password, are you guys suggesting the use of two seperate STS? Or is there a way to implement the CustomSecurityTokenHandler to take care of both case?

     

    Thanks!

     

    Friday, January 30, 2009 4:30 PM
  • You will certainly not need to STS' as the authentication is separated to the custom handler.
    You will not need a custom security token handler neither as your handler could implement both cases; the main challenge you will have, if I remember correctly, is to identify which one to use, but once you have, the code neotom posted shows how to handle all three cases. 



    Yossi Dahan | [To help others please mark replies as answers if you found them helpful]
    Saturday, January 31, 2009 8:43 AM