Why does WS-Federation require a CustomJwtSecurityTokenHandler

Jun 13, 2014 at 1:16 AM
Having the following issue, while configuring wsfederation middleware to work with ACS.
By my understanding the following should be enough:
 var wsfederationOptions = new WsFederationAuthenticationOptions {
                MetadataAddress = System.Configuration.ConfigurationManager.AppSettings["ida:FederationMetadataLocation"],
                Wtrealm = System.Configuration.ConfigurationManager.AppSettings["ida:Realm"],
                };
app.SetDefaultSignInAsAuthenticationType(WsFederationAuthenticationDefaults.AuthenticationType);
            app.UseCookieAuthentication(new CookieAuthenticationOptions() { AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType });
app.UseWsFederationAuthentication(wsfederationOptions);
But for some reason, when I do this I get an exception saying that there are no token handlers configured for binarysecuritytoken (the token returned from ACS... I've configured ACS to return JWT tokens).
Is there no workaround to writing a CustomJwtSecurityTokenHandler?
Coordinator
Jun 17, 2014 at 5:52 PM
I asked a co-worker and they said:
"Looks like the token is signed with a symmetric key, when you read the metadata, the signing key in the TokenValidationParameters is set to certificate. Updating the signing key in Token validation parameters should work."
Jun 17, 2014 at 6:56 PM
Edited Jun 17, 2014 at 6:56 PM
BTW, you're not using SetDefaultSignInAsAuthenticationType and AuthenticationType correctly.

See this topic for a working sample: https://katanaproject.codeplex.com/discussions/547633
Jun 17, 2014 at 11:39 PM
Thanks for pointing that out PinpointTownes (pun intended ofcourse), I did make the change as Tratcher had pointed that out to me in another thread.

The default JwtSecurityTokenHandler doesn't seem to work with JWT tokens issued by ACS. the problem seems to be with the CanReadToken method which returns false, and needs to be overwritten. Hence the need for a CustomJwtSecurityTokenHandler. I did try adding a new TokenValidationparameter (set certificate validator to None & IssuerSigningKey to an inmemorysymmetricsecurityKey. but it didn't seem to work.

Here is the error that I get when I try to use the default JwtSecurityTokenHandler
DX10201: None of the the SecurityTokenHandlers could read the 'securityToken': '<wsse:BinarySecurityToken wsu:Id="_8d83e9fb-3a11-409b-814e-6e4c0537ef58-CB01A25E29EC21C468502E7E709E6D59" ValueType="urn:ietf:params:oauth:token-type:jwt" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">ZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKSVV6STFOaUo5LmV5SmhkV1FpT2lKb2RIUndjem92TDJ4dlkyRnNhRzl6ZERvME5ETXdNUzhpTENKcGMzTWlPaUpvZEhSd2N6b3ZMMjF2Ym1sMGIzSjBaWE4wTG1GalkyVnpjMk52Ym5SeWIyd3VkMmx1Wkc5M2N5NXVaWFF2SWl3aWJtSm1Jam94TkRBek1EUTNNekU1TENKbGVIQWlPakUwTURNd05EYzVNVGtzSW01aGJXVnBaQ0k2SWpsSGNrWldUR1puY0ZacVpIWmtZamxIU2xaMlNEQmhWM05wVTNnMGIyRlBOVTVNYlRoRlpWQjZVM005SWl3aWFXUmxiblJwZEhsd2NtOTJhV1JsY2lJNkluVnlhVHBYYVc1a2IzZHpUR2wyWlVsRUluMC4tcERXZVJtblVDYjU0aGxGZUViU0g4SHl2WXRhZEpSQjdJS0kzbGowenlj</wsse:BinarySecurityToken>'.
Here is the customJwtHandler class that I had created. As you can see, it doesn't really handle the token as much as override the CanReadToken and pass on an XmlReader to the base ReadToken method. So am wondering why I can't use the regular JwtSecurityTokenHandler, is there a bug in it? or is it by design?
  public class CustomJwtSecurityTokenHandler : JwtSecurityTokenHandler
        {

            private const string BinarySecurityToken = "wsse:BinarySecurityToken";
            private const string ValueType = "ValueType";

            public override SecurityToken ReadToken(string tokenString)
            {
                var reader = XmlReader.Create(new StringReader(tokenString));

                if (!tokenString.Contains("Binary"))
                {
                    return base.ReadToken(tokenString);
                }

                return base.ReadToken(reader);
            }

            public override bool CanReadToken(string tokenString)
            {
                return true;
            }

            public override bool CanReadToken(XmlReader reader)
            {
                var canRead = false;

                if (reader != null)
                {
                    if (reader.IsStartElement(BinarySecurityToken) && (reader.GetAttribute(ValueType) == "urn:ietf:params:oauth:token-type:jwt"))
                    {
                        canRead = true;
                    }
                }

                return canRead;
            
            }
        }
Jun 26, 2014 at 12:59 AM
Hi Thratcher,
Is it possible to get some information as to why the default jwt security token handler is not working? Per your recommendation, here are the updates I made to the ws-federation configuration:
var SymmetricKey = System.Configuration.ConfigurationManager.AppSettings["ida:SymmetricKey"];

            
            var wsfederationOptions = new WsFederationAuthenticationOptions
            {
                MetadataAddress = System.Configuration.ConfigurationManager.AppSettings["ida:FederationMetadataLocation"],
                Wtrealm = System.Configuration.ConfigurationManager.AppSettings["ida:Realm"],

                SecurityTokenHandlers = new SecurityTokenHandlerCollection(),
                TokenValidationParameters = new TokenValidationParameters()
                {
                    IssuerSigningKey = new InMemorySymmetricSecurityKey(Convert.FromBase64String(SymmetricKey)),
                    CertificateValidator = System.IdentityModel.Selectors.X509CertificateValidator.None,

                }
               
            };
            wsfederationOptions.SecurityTokenHandlers.Add(new JwtSecurityTokenHandler());
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.UseCookieAuthentication(new CookieAuthenticationOptions());

            app.UseWsFederationAuthentication(wsfederationOptions);
Am I doing something wrong here? I've looked at the thread PinPointTownes had referenced, but it doesn't seem to be the cause of the problems I am having. The exact error thrown by the web app is as follows:
IDX10201: None of the the SecurityTokenHandlers could read the 'securityToken': '<wsse:BinarySecurityToken wsu:Id="_bf2d00f4-c086-4cec-913f-846ef34037c6-63DDAEEA77CD26E7B878448416E1CE86" ValueType="urn:ietf:params:oauth:token-type:jwt" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">ZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKSVV6STFOaUo5LmV5SmhkV1FpT2lKb2RIUndjem92TDJ4dlkyRnNhRzl6ZERvME5ETXdNUzhpTENKcGMzTWlPaUpvZEhSd2N6b3ZMMjF2Ym1sMGIzSjBaWE4wTG1GalkyVnpjMk52Ym5SeWIyd3VkMmx1Wkc5M2N5NXVaWFF2SWl3aWJtSm1Jam94TkRBek56UXpOVE13TENKbGVIQWlPakUwTURNM05EUXhNekFzSW01aGJXVnBaQ0k2SWpsSGNrWldUR1puY0ZacVpIWmtZamxIU2xaMlNEQmhWM05wVTNnMGIyRlBOVTVNYlRoRlpWQjZVM005SWl3aWFXUmxiblJwZEhsd2NtOTJhV1JsY2lJNkluVnlhVHBYYVc1a2IzZHpUR2wyWlVsRUluMC5GR1FQQ2l5SXVJMG9pLVZNVHZ4V2g1NjQyTTRhWUtfbUlJa2RjQmctSUMw</wsse:BinarySecurityToken>'.
Here is the stacktrace thrown by the web app. It looks like something is not getting configured correctly, but I am unable to figure out what it is.
[SecurityTokenValidationException: IDX10201: None of the the SecurityTokenHandlers could read the 'securityToken': '<wsse:BinarySecurityToken wsu:Id="_bf2d00f4-c086-4cec-913f-846ef34037c6-63DDAEEA77CD26E7B878448416E1CE86" ValueType="urn:ietf:params:oauth:token-type:jwt" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">ZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKSVV6STFOaUo5LmV5SmhkV1FpT2lKb2RIUndjem92TDJ4dlkyRnNhRzl6ZERvME5ETXdNUzhpTENKcGMzTWlPaUpvZEhSd2N6b3ZMMjF2Ym1sMGIzSjBaWE4wTG1GalkyVnpjMk52Ym5SeWIyd3VkMmx1Wkc5M2N5NXVaWFF2SWl3aWJtSm1Jam94TkRBek56UXpOVE13TENKbGVIQWlPakUwTURNM05EUXhNekFzSW01aGJXVnBaQ0k2SWpsSGNrWldUR1puY0ZacVpIWmtZamxIU2xaMlNEQmhWM05wVTNnMGIyRlBOVTVNYlRoRlpWQjZVM005SWl3aWFXUmxiblJwZEhsd2NtOTJhV1JsY2lJNkluVnlhVHBYYVc1a2IzZHpUR2wyWlVsRUluMC5GR1FQQ2l5SXVJMG9pLVZNVHZ4V2g1NjQyTTRhWUtfbUlJa2RjQmctSUMw</wsse:BinarySecurityToken>'.]
   Microsoft.IdentityModel.Extensions.SecurityTokenHandlerCollectionExtensions.ValidateToken(SecurityTokenHandlerCollection tokenHandlers, String securityToken, TokenValidationParameters validationParameters, SecurityToken& validatedToken) +650
   Microsoft.Owin.Security.WsFederation.<AuthenticateCoreAsync>d__1e.MoveNext() +4143
   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() +22
   Microsoft.Owin.Security.WsFederation.<AuthenticateCoreAsync>d__1e.MoveNext() +6762
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +93
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +52
   System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +24
   Microsoft.Owin.Security.Infrastructure.<BaseInitializeAsync>d__0.MoveNext() +809
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +93
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +52
   System.Runtime.CompilerServices.TaskAwaiter.GetResult() +21
   Microsoft.Owin.Security.Infrastructure.<Invoke>d__0.MoveNext() +427
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +93
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +52
   System.Runtime.CompilerServices.TaskAwaiter.GetResult() +21
   Microsoft.Owin.Security.Infrastructure.<Invoke>d__0.MoveNext() +937
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +93
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +52
   System.Runtime.CompilerServices.TaskAwaiter.GetResult() +21
   Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.<RunApp>d__5.MoveNext() +287
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +93
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +52
   System.Runtime.CompilerServices.TaskAwaiter.GetResult() +21
   Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.<DoFinalWork>d__2.MoveNext() +272
   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() +22
   Microsoft.Owin.Host.SystemWeb.Infrastructure.ErrorState.Rethrow() +33
   Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.StageAsyncResult.End(IAsyncResult ar) +150
   Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.IntegratedPipelineContext.EndFinalWork(IAsyncResult ar) +42
   System.Web.AsyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +415
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155
Jun 27, 2014 at 8:13 PM
Hi masterkidan,

This is because of a bug in JwtSecurityTokenHandler, https://github.com/MSOpenTech/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/18.

You can use the below as a workaround
public class CustomJwtSecurityTokenHandler : JwtSecurityTokenHandler
{
    public override bool CanReadToken(string tokenString)
    {
        bool response = base.CanReadToken(tokenString);

        if (!response)
        {
            // see if the token is in the BinarySecurityToken format
            XmlTextReader reader = new XmlTextReader(new StringReader(tokenString));
            response = base.CanReadToken(reader);
        }

        return response;
    }

    public override ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
    {
        // assumes that the token is in the BinarySecurityToken format.
        XmlTextReader reader = new XmlTextReader(new StringReader(securityToken));

        reader.MoveToContent();
        reader.Read();
        string token = Encoding.UTF8.GetString(Convert.FromBase64String(reader.Value));

        ClaimsPrincipal claimsPrincipal = base.ValidateToken(token, validationParameters, out validatedToken);
        return claimsPrincipal;
    }
}

Marked as answer by masterkidan on 6/27/2014 at 2:49 PM