At my current assignment we’re working on a solution which among others consists of a custom Security Token Service which is used for the authentication of users in a web portal. In our case we’ve created an active STS by using Windows Identity Foundation and WCF. We’re now working a demo web application which uses the STS (and some other functionality we’re building) to show the team which creates the portal how they should consume the STS.
Since there is almost NO decent documentation about WIF, I started googling. The problem which I ran into was that the information I found was almost all about scenario’s with a passive STS. I found a bit information about consuming an active STS but all not complete, so I’ll put all the steps I had to take right here, so it can help some others.
First of all, I eventually found this blog post, which made it almost work for me. There was just some information missing which caused me to search for another 1 – 2 hours.
These are the steps to take when you want to consume an active STS from a web application:
- Add a reference to the Microsoft.IdentityModel assembly (WIF).
- Add the definition of the microsoft.IdentityModel config section to your config like this (check the correct version of the dll of course):
<section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
- Add the following two HttpModules to your config (when using IIS7, add them to you system.webserver section, otherwise to you system.web section):
<add name="WSFederationAuthenticationModule" type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
- The authentication mode should be set to None:
<authentication mode="None" />
- Add the configuration for the microsoft.IdentityModel section, for instance:
<microsoft.identityModel> <service> <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"> <trustedIssuers> <add thumbprint="{Add the thumbprint of the certificate used by your STS, for instance: 80481e4041bd6758400c62e2c811831b98eed561}" name="{Add the name of the certificate, for instance: CN=devsts}" /> </trustedIssuers> </issuerNameRegistry> <audienceUris> <add value="{Add the applies to url of your web application}"/> </audienceUris> <federatedAuthentication> <wsFederation passiveRedirectEnabled="false" issuer="{The address of the STS, for instance: https://devsts/mySts.svc}" realm="{The applies to address of your web application, for instance: http://myrelyingparty.nl}" persistentCookiesOnPassiveRedirects="true" /> <cookieHandler requireSsl="false" /> </federatedAuthentication> <serviceCertificate> <certificateReference x509FindType="FindByThumbprint" findValue="{The certificate used by your STS, for instance: 80481e4041bd6758400c62e2c811831b98eed561}" storeLocation="LocalMachine" storeName="My"/> </serviceCertificate> </service> </microsoft.identityModel>As you can see, you register your information about the certificate being used by your sts and the information about your application, the relying party.
The line about the cookieHandler was the one that caused me some problems because I didn’t have that. The problem is that my local site was working on http and not https, but the created cookies required https. I didn’t have this line at first, which had the effect that the session cookie was not maintained over postbacks. - After you’ve configured everything, you can use the following code to consume your STS and get an IClaimsIdentity:
// authenticate with WS-Trust endpoint var factory = new WSTrustChannelFactory( new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential), new EndpointAddress("<a href="https://devsts/MySts.svc">https://devsts/MySts.svc</a>")); factory.Credentials.UserName.UserName = usernameField.Text; factory.Credentials.UserName.Password = passwordField.Text; var channel = factory.CreateChannel(); var rst = new RequestSecurityToken { RequestType = RequestTypes.Issue, AppliesTo = new EndpointAddress("http://myrelyingparty.nl/"), KeyType = KeyTypes.Bearer }; var genericToken = channel.Issue(rst) as GenericXmlSecurityToken; // Now you parse and validate the token which results in a claimsidentity var handlers = FederatedAuthentication.ServiceConfiguration.SecurityTokenHandlers; var token = handlers.ReadToken(new XmlTextReader(new StringReader(genericToken.TokenXml.OuterXml))); var identity = handlers.ValidateToken(token).First(); // Create the session token using WIF and write the session token to a cookie var sessionToken = new SessionSecurityToken(ClaimsPrincipal.CreateFromIdentity(identity)); FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionToken); //Perform some redirect Response.Redirect("~/secure/default.aspx");
In our situation, this was not complete, since we need the SAML token received from the STS furtheron to authenticate to WCF services which we consume. After using reflector and trying something out, it can be easily done by changing just two lines of code. I can be done by the following:
var identity = handlers.ValidateToken(token).First();
Thread.CurrentPrincipal = new ClaimsPrincipal(new IClaimsIdentity [] { new ClaimsIdentity(identity.Claims, token) });
The code itself looks probably a bit strange, since we get a ClaimsIdentity from the ValidateToken and then we create another ClaimsIdentity. I hoped that WIF would have used this way to construct the identity, of at least provide an overload or something to do this. I created the identity like this, since the securitytoken is now availalbe in the BootstrapToken property of the ClaimsIdentity. At first we were thinking that we had to keep the security token in session but that is not necessary when you do it like this. Now we can access it simply with the following lines of code:
var identity = Thread.CurrentPrincipal.Identity as ClaimsIdentity; var theOriginalSecurityToken = identity.BootstrapToken;
I hope this helps somebody else. It would have saved me a lot of time if I could have found this information somewhere.
Update (17-8-2010)
We’ve been using the code as listed above, but we ran into some problems, because the retrieved token is a GenericXmlSecurityToken which caused problems when supplying it to our backend services.
After some searching I found that it is possible to get the BootstrapToken property filled by WIF, but you need to set a configuration switch (some decent documentation would really be helpfull). All you have to do is change the following in your web application configuration (add the saveBootstrapTokens attribute):
<microsoft.identityModel> <service saveBootstrapTokens="true> <issuerNameRegistry ……
The code to creating your principal is then the following:
var identity = handlers.ValidateToken(token).First();
Thread.CurrentPrincipal = new ClaimsPrincipal(new [] { identity });
Now the BootstrapperToken is a SamlSecurityToken which is exactly what we want to be able to authenticate to the backend services. I’ll show in a new post how we have all this tied together.
Good Post…
I tried in the same way ut i et exception on statement ..
var genericToken = channel.Issue(rst) as GenericXmlSecurityToken;
The exception is…
{“Could not establish trust relationship for the SSL/TLS secure channel with authority ‘”}
Any suggestion
Regards
Comment by Sharad — December 30, 2010 @ 16:43
I tried in the same way ut i et exception on statement ..
var genericToken = channel.Issue(rst) as GenericXmlSecurityToken;
The exception is…
{“Could not establish trust relationship for the SSL/TLS secure channel with authority ‘”}
Any suggestion
Regards
Comment by SharadK — December 30, 2010 @ 16:44
This error has to do with the certificate you are using for your SSL connection. It seems that it’s an invalid certificate. If it’s a self signed certificate, then you should add it to your trusted certificates using mmc.exe. A good way to check if the certificate is then valid is to navigate in your browser to the endpoint of the STS and check if you don’t have any certificate errors / warnings.
Please let me know if this fixed it for you.
Comment by koenwillemse — December 31, 2010 @ 00:32
Its a Good Article.
I tried the above, and I get the following error for the line
var genericToken = channel.Issue(rst) as GenericXmlSecurityToken;
The error is “there was no endpoint listening at . and the inner exception is “the Remote server returned an error 404 not found.”.
Any suggestions on how to resolve the same.
Thanks
Comment by Himanshu — February 1, 2011 @ 08:55
Resolved the error and was bale to authenticate as well.
Thanks
Comment by Himanshu — February 1, 2011 @ 11:22
How did you resolve this issue. I am having the same problem.
Comment by Chris — March 17, 2011 @ 22:16
Can yo reach the endpoint using your browser? or do you get certificate error?
Comment by koenwillemse — March 17, 2011 @ 22:26
I can pull it up in a browser
Comment by Chris — March 17, 2011 @ 22:59
I can see the “SecurityTokenService Service” definition screen and can access the wsdl. I was getting cert errors, but I resolved those and now i get the following through the factory channel call:
There was no endpoint listening at http://localhost/STSService/Service.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details.
Inner Exception:
The remote server returned an error: (404) Not Found.
Comment by Chris — March 17, 2011 @ 23:03
Just happened to see the post today, I hope you have resolved this by now. In case you have not and for other people facing similar issue please check the endpoint address in web.config , in my case it was IWSTrust13, based on that we need to change the endpointaddress in the code, so the code mentioned in point 6 will change to
var factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress(“https://localhost/STSService/Service.svc/IWSTrust13″));
which will resolve the issue.
Comment by Himanshu — May 2, 2011 @ 12:59
Hi, I am getting a timeout exception at the line channel.Issue(rst). I could not find a way to increase the timeout way config, since the binding does not seem to work in web.config.
The service.svc comes up fine and even the wsdl.
Any idea what is going wrong here?
Comment by Ghanshyam — March 22, 2011 @ 15:18
It’s difficult to point you to a specific problem. It’s probably a ‘standard’ WCF problem. Are your bindings, endpoints etc correctly declared (both on the server and client)?
Comment by koenwillemse — March 22, 2011 @ 20:41
Hi, I got the issue fixed by hosting in IIS rather than in web development server.
I think the last paragraph in the article below was what I would have needed to get it working:
http://cloudythoughts.siadis.com/windows-azure/windows-azure-appfabric/creating-a-custom-sts-with-windows-identity-foundation
Comment by Ghanshyam — March 31, 2011 @ 10:33
For the exception is…
{“Could not establish trust relationship for the SSL/TLS secure channel with authority ‘”}
You must generate the certificate with Issue Name equal that url service STS.
Comment by rodrijp — May 12, 2011 @ 16:59
In your update of 17-8-2010: The following is not fully correct : … = new ClaimsPrincipal(new IClaimsIdentity [] { identity.Claims });
This should be … = new ClaimsPrincipal(new [] { identity });
Comment by William — June 10, 2011 @ 15:40
Thanks for the comment. I updated the update
.
Comment by koenwillemse — July 23, 2011 @ 20:37
I had the same problem with STS returning remote server returned an error: (404) Not Found in calling .Issue. I has setup SSL on the STS and RP..and also changed the address in the service web.config to https:// …but I needed to change the security mode from “Message” to “TransportWithMessageCredential” – it was first then that the endpoint actually changed in the WSDL.
If in doubt ..look at the address under wsdl:port in the WSDL of the STS. ..DOOOH
Comment by JAXN — July 15, 2011 @ 13:41