Not everybody has the luxury of being able to start over and build the new & modern version of their software from scratch. Many people I speak to have existing investments in WCF and their “old-school” desktop/intranet architecture.
Moving to an internet/mobile world while preserving the existing services is not easy because the technologies (and in my case the security technologies) are fundamentally incompatible. Your new mobile/modern clients will not be seamlessly able to request tokens from your existing WS-Trust STS and SOAP is not really compatible with OAuth2. So what to do?
You could try to teach your WS-Trust STS some basic HTTP based token service capabilities and continue using SAML tokens. You could provide some sort of SAML/JWT conversion mechanism and create Web APIs for your new clients that proxy / convert to the WCF world. Or you could provide to separate token services and establish trust between them. All approaches have their own advantages and disadvantages.
For a project I am currently working on I chose a different approach – get rid of the old WS-Trust STS altogether, replace it with an OAuth2 authorization server (IdentityServer3) and make your WCF services consume JWT tokens. This way both old and new clients can request tokens via OAuth2 and use them with either existing WCF services and the new Web APIs (which ultimately will be also used in the desktop version of the product). How does that work?
Requesting the token
The OAuth2 resource owner flow is what comes closest to WS-Trust and it is easy to replace the WCF WSTrustChannel code with that. Going forward the web view based flows actually give more features like external IdPs etc. but need a bit more restructuring of the existing clients. New clients can use them straight away.
Sending the token
This is the tricky part. WCF can not deal with JWTs directly since they are not XML based. You first need to wrap them in an XML data structure and the typical approach for that is to use a so called binary security token. This worked fine at some point but the latest version of WCF and the JWT token handler don’t seem to work together anymore (here’s a nice write up from Mickael describing the problem).
Since WCF is really done – I did not expect anyone to fix that bug anytime soon, so I needed a different solution.
Another XML container data structure that is well tested and does the job equally well is SAML – so I simply created a minimal SAML assertion to hold the JWT token.
static GenericXmlSecurityToken WrapJwt(string jwt)
var subject = new ClaimsIdentity("saml");
subject.AddClaim(new Claim("jwt", jwt));
var descriptor = new SecurityTokenDescriptor
TokenType = TokenTypes.Saml2TokenProfile11,
TokenIssuerName = "urn:wrappedjwt",
Subject = subject
var handler = new Saml2SecurityTokenHandler();
var token = handler.CreateToken(descriptor);
var xmlToken = new GenericXmlSecurityToken(
Since we are using SAML solely as a container, there is no signature, no audience URI and just a single attribute statement containing the JWT.
After that you can use the wrapped JWT with the CreateChannelWithIssuedToken method over a federation binding:
var binding = new WS2007FederationHttpBinding(
binding.Security.Message.EstablishSecurityContext = false;
binding.Security.Message.IssuedKeyType = SecurityKeyType.BearerKey;
var factory = new ChannelFactory<IService>(
var channel = factory.CreateChannelWithIssuedToken(xmlToken);
Validating the token
On the service side I sub-classed the SAML2 security token handler to get the SAML deserialization. In the ValidateToken method I retrieve the JWT token from the assertion and validate it.
Since I have to do the validation manually anyways, I wanted feature parity with our token validation middleware for Web API which means that the token handler can auto-configure itself using the OpenID Connect discovery document as well as do the scope validation.
new IdentityServerWrappedJwtHandler("https://localhost:44333/core", "write"));
The end result is that both WCF and Web API can now consumes JWT tokens from IdentityServer and the customer can smoothly migrate and extend their architecture.
The POC can be found here. It is “sample quality” right now – feel free to make it more robust and send me a PR.