Manually configuring OWIN WS-Federation middleware and accepting encrypted tokens

In my previous post, I showed how to do a simple configuration of WS-Federation using WIF, or whatever it is called now that it is part of the framework, to enable federated authentication in ASP.NET. Something that was previously done using a tool, but now either has to be done at the start of the application, or manually.

But what about OWIN? As all new security stuff is moving to OWIN, how do we get it to work there? Well, by default, it is ridiculously simple. And that has been the whole goal with this new model.

By default, all you need to do is get hold of a couple of NuGet packages. In this case, when integrating with WS-Federation, the package to get is Microsoft.Owin.Security.WsFederation. This package will install a whole heap of other NuGet packages that you will need. But if you just choose this one, it will include what you need and you don’t have to worry. However, you do also need Microsoft.Owin.Security.Cookies and Microsoft.Owin.Host.SystemWeb to get OWIN running in your application, and cookie authentication to work.

Warning: As of today, the WsFederation NuGet package is a release candidate (RC-2 at the moment), so you will need to keep that in mind when adding it to your solution. If you use the package manager console, you will need to add “-pre”…and if you use the UI, you have to remember to select “Include Prereleases”

Once you got those packages, you create a Startup class and add the following piece of code

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
});
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
MetadataAddress = "https://sts/FederationMetadata/2007-06/metadata.xml",
Wtrealm = "urn:myRealm",
SignInAsAuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = "urn:myRealm"
}
});

Ok, so “ridiculously simple” might be an exaggeration. But it isn’t that hard… So, what are we doing here? Well, we start off by telling the application to use cookies for authentication. This is used once the token has been validated… Next we tell it to use WS-Federation, and we pass in some configuration. In this case, we are telling it a few things. First off, we give it the address to the federation metadata document. This will be used to figure out all the other configuration that is needed to get it working. Next, we tell it the realm to use, and the authentication type to use.

The authentication type is used by the cookie middleware to figure out a bunch of things, so it is important that you have the same value here as you have for the authentication type in the cookie authentication registration.

Finally, we add some token validation parameters. In this case, I add only an audience to validate it against. In more complex scenarios, you would probably add more stuff here.

Ok, that is a bit simpler than having to use config files and stuff to get it configured. And on top of that, it is based on OWIN, so it will work with any OWIN based solution.

But what happens if you want to have encrypted tokens? Well, then it kind of falls apart…at the moment at least…

If you don’t manually add the SecurityTokenHandlers to use, the middleware will default to adding a bunch of standard ones. Right now, those standard ones are JwtSecurityTokenHandler, Saml2SecurityTokenHandler and SamlSecurityTokenHandler.

Warning! A bit of caution here! The SamlSecurityTokenHandler and Saml2SecurityTokenHandler in this case are from the namespace Microsoft.IdentityModel.Tokens and not System.IdentityModel.Tokens. They inherit from SecurityTokenHandler, but they also implement ISecurityTokenValidator, which is a new thing. The ones in System.IdentityModel do not implement that interface, and will not work in this pipeline.

As the “old” SecurityTokenHandlers don’t work in the new pipeline, you would kind of expect there to be nice replacements for them. Unfortunately, at the moment, there isn’t a SecurityTokenHandler in the Microsoft.IdentityModel namespace that can handled encrypted tokens. So how do we solve this?

Well, we will just have to manually add some custom token handlers to do the job. Let’s start with the encryption…

As there is an EncryptedSecurityTokenHandler in the System.IdentityModel namespace, this seems like a great place to start. So I start out but subclassing this handler. I call the new version EncryptedSecurityTokenHandlerEx. And as the class will need a SecurityTokenResolver to do the decryption, I take one of those as a parameter to the constructor. And finally, I add the ISecurityTokenValidator interface to my class.

It looks like this

public class EncryptedSecurityTokenHandlerEx : EncryptedSecurityTokenHandler, ISecurityTokenValidator
{
public EncryptedSecurityTokenHandlerEx(SecurityTokenResolver securityTokenResolver)
{
Configuration = new SecurityTokenHandlerConfiguration
{
ServiceTokenResolver = securityTokenResolver
};
}

public override bool CanReadToken(string securityToken) { ... }

public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters,
out SecurityToken validatedToken) { ... }

public int MaximumTokenSizeInBytes { get; set; }
}

Ok, nothing too complicated so far. The only thing is that I set the Configuration property to a new instance, and configure it to use the supplied SecurityTokenResolver.

One thing to note is that I am overriding the CanReadToken() method. This is part of the ISecurityValidator interface. “Unfortunately”, the base class (SecurityTokenHandler) implements a method with the same signature, but just throws an exception…

Ok, so what do I do in my methods? Well, not much. I mostly delegate to the base class, but to the correcly implemented overloads. In the CanReadToken() method, I convert the incoming string to an XmlTextReader, and call the method overload from the base class that takes a XmlReader instead of a string.

Remember, I am just trying to get the handler to work nicely with the new pipeline. I don’t want to change the way it works. So delegating everything to the base class I great. I just need to make sure that I find methods that work. in this case, converting from string to XmlReader is all that is needed.

public override bool CanReadToken(string securityToken)
{
return base.CanReadToken(new XmlTextReader(new StringReader(securityToken)));
}

In the ValidateToken() method, there is a bit more work to do. First of all, the base class doesn’t have a method that corresponds perfectly to this. So I have to start by calling ReadToken() instead. And I also need to remember to call the ReadToken() version that takes 2 parameters. The single parameter one will just throw an exception as it is implemented by SecurityTokenHandler and not the EncryptedSecurityTokenHandler class.

Next, I take my decrypted token and validate it. However, the EncryptedSecurityTokenHandler doesn’t include validation. Instead, it relies on another handler in the collection of handlers being able to validate it. So I will do the same.

Luckily, adding a handler to a SecurityTokenHandler will make sure that the handler gets access to the containing collection through the ContainingCollection property. Using this, I validate the the token, and return a new ClaimsPrincipal.

If there is no ContainingCollection for some reason, I call the base class, which will throw an exception…

It looks like this

public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
{
validatedToken = ReadToken(new XmlTextReader(new StringReader(securityToken)), Configuration.ServiceTokenResolver);
if (ContainingCollection != null)
{
return new ClaimsPrincipal(ContainingCollection.ValidateToken(validatedToken));
}
return new ClaimsPrincipal(base.ValidateToken(validatedToken));
}

Cool, so now I have a way to decrypt my incoming token and redirecting it to some other handler for processing. The problem is that I am still in need of that other hander…

Luckily, that part is already in the OWIN WS-Federation NuGet package. So I don’t have to create my own. All I need to do is to add the existing one to the collection. Or can I? Actually you can’t. Well, you might be able to, but as the SecurityTokenHandlerCollection is based in the “old” ways, it depends on the old methods from the handlers. And in this case, it means that I can’t use the new handler out of the box, and subclassing and modifying that class is harder than subclassing the “old” handler. So that is what I will do.

Once again I create a new SecurityTokenHandler class and implement the ISecurityTokenValidator interface. I name it SamlSecurityTokenHandlerEx and inherit from the System.IdentityModel.Tokens.SamlSecurityTokenHandler.

This class does not need any special configuration so I don’t take any parameters in the constructor… Actually I will need to configure it, but I will do that when I use it instead…

Once again I need to override the CanReadToken() and implement the ValidateToken() method. Both of these implementations are almost identical to the one I just showed. So I won’t go in to too much detail, and instead just show the code

public class SamlSecurityTokenHandlerEx : SamlSecurityTokenHandler, ISecurityTokenValidator
{
public override bool CanReadToken(string securityToken)
{
return base.CanReadToken(XmlReader.Create(new StringReader(securityToken)));
}

public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters,
out SecurityToken validatedToken)
{
validatedToken = ReadToken(new XmlTextReader(new StringReader(securityToken)), Configuration.ServiceTokenResolver);
return new ClaimsPrincipal(ValidateToken(validatedToken));;
}

public int MaximumTokenSizeInBytes { get; set; }
}

Ok, not that I have my handlers, how do I go about getting them hooked up and the application running then? Well, The setup is pretty similar to the one used at the beginning of the post. However, instead of pointing to a federation metadata file, I set the configuration manually, which is a bit more complicated and for obvious reasons a bit more like the old way.

First of all, I need to define what audience restrictions I have, and what issuers I trust. I do this by creating an AudienceRestriction instance, and a ConfigurationBasedIssuerNameRegistry instance, and populating them with the required information. Like this

public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
});

var audienceRestriction = new AudienceRestriction(AudienceUriMode.Always);
audienceRestriction.AllowedAudienceUris.Add(new Uri("urn:realm"));

var issuerRegistry = new ConfigurationBasedIssuerNameRegistry();
issuerRegistry.AddTrustedIssuer("xxxxxxxxxxxxxxxxxxxxxxxxx", "http://sts/");

...
}

Next I call the UseWsFederationAuthentication() method like before. But this time, I put in a heap of configuration instead of just point to a federation metadata file.

The first part of the config is in regard to what realm it is, what reply address should be used and where the user should go to get a token. And I also need to tell it what authentication type it is. I don’t know why, as this is passed into the constructor, but it needs to be passed in as part of a TokenValidationParameters object as well…

app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions(WsFederationAuthenticationDefaults.AuthenticationType)
{
Wtrealm = "http://wsfedtest/",
Wreply = "http://localhost:1949/secure",
Configuration = new WsFederationConfiguration() { TokenEndpoint = "http://sts.kmd/SignIn" },
TokenValidationParameters = new TokenValidationParameters
{
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
},
...
});

The last bit is to get my own SecurityTokenHandlers in to the pipeline instead of the default ones. This is quite easy. I just have to set the SecurityTokenHandlers property of the WsFederationAuthenticationOptions. It needs to be set to a SecurityTokenHandlerCollection containing all the handlers I want to use. However, they also need to be configured before being put in there. They will not be reading any of the “common” configuration that you can set using the TokenValidationParameters. At least not the way I built it…

app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions(WsFederationAuthenticationDefaults.AuthenticationType)
{
...
SecurityTokenHandlers = new SecurityTokenHandlerCollection
{
new EncryptedSecurityTokenHandlerEx(new X509CertificateStoreTokenResolver(StoreName.My,StoreLocation.LocalMachine)),
new SamlSecurityTokenHandlerEx
{
CertificateValidator = X509CertificateValidator.None,
Configuration = new SecurityTokenHandlerConfiguration()
{
AudienceRestriction = audienceRestriction,
IssuerNameRegistry = issuerRegistry
}
}
}
});

As you can see, I create and add an EncryptedSecurityTokenHandlerEx instance giving it a SecurityTokenResolver pointing to my encryption cert. I then create and add an SamlSecurityTokenHandlerEx instance, setting the required configuration. In this case that means disabling certificate validation and setting the AudienceRestriction and IssuerNameRegistry to my previously created and configured instances.

Note: Reading the encryption cert from the certificate store is the regular way to do it. However, if you are publishing your app to a location where this doesn’t work, for example an Azure Website, you can just create your own SecurityTokenResolver that reads the cert from somewhere else…

That’s pretty much it!

Ok, that should be enough to get everything going! As usual, there is some sample code available here: DarksideCookie.Owin.WsFederation.Encrypted.zip (482.97 kb)

Just note that there is a bit of “external” configuration to get it running, such as adding certs etc... And it requires that you have an STS available. So look through the code and see if you can get it working…

The sample code only includes code for setting up the encrypted tokens tuff. Plain vanilla stuff should be pretty well documented on-line…

Cheers!

Comments (9) -

Thank you very much for writing this.  This was really helpful.  One modification I had to do for signing validation was to inherit from Saml2SecurityTokenHandler as my tokens were saml2.

Please replace instances of  WsFederationAuthenticationDefaults.AuthenticationType with CookieAuthenticationDefaults.AuthenticationType. WsFederationAuthenticationDefaults.AuthenticationType applies to WsFederationAuthenticationOptions.AuthenticationType, and having multiple middleware with the same *Options.AuthenticationType causes issues with Challenge, SignIn, etc.

I tried to do like you describe. But I met really annoying problem: "System.Security.Cryptography.CryptographicException: Invalid provider type specified". I had hard time to search for reason of this problem. I check many different opinions but didn't find root cause. Maybe you know what it can be?

Hi, nice post, but i get stock with this error

1)  ID4175: The issuer of the security token was not recognized by the IssuerNameRegistry. To accept security tokens from this issuer, configure the IssuerNameRegistry to return a valid name for this issuer.

crash on this line :return new ClaimsPrincipal(ContainingCollection.ValidateToken(validatedToken));
And i can dig and see claims and claim values. So it look like some decryption occur.


No encryption, work just fine.

Any help

Hi David,

Did you have any luck resolving this? I'm currently hitting the same issue.

Thanks,
James

Sean Brophy 10/16/2016 1:27:59 AM

Thank you so much! After I got over being overwhelmed by the SSL stuff, I followed your guide and it worked, PERFECTLY.

Awesome and thanks so much! Learned a ton in the process.

Sean Brophy 3/17/2017 12:14:22 AM

Hey again,

Any chance of an updated guide? Library updates have broken this approach to manual authentication.

Oh, it's been ages since I did this... Sorry to hear that it is not working for you! I'll see what I can do, but I have a ton of things on my plate at the moment unfortunately. Are there no WS-Fed middlewares out there now? It would surprise me if no one had created a NuGet package yet. But if that is the case, I'll see what I can do!

Noel Dsouza 6/27/2017 2:09:45 PM

I downloaded the zip file that you posted . I was curious why you don't have the config for system.identityModel in your web.config.

Add comment