Lately, I have been playing around with WIF to build an Active STS. I didn’t have access to necessary tools to build this on .NET 4.5 and I used .NET 4.0 to start with, this difference is significant because WIF is now part of .NET 4.5 and deeply embedded with .NET libraries, though conversion from this 4.0 to 4.5 shouldn’t be too hard.
Few weeks back I wrote about WIF here, its basics and the difference between .NET 4.0 and 4.5.
This article discussed following
- Implement Active STS with WIF & .NET 4.0
- Consuming Active STS in a MVC Application
Alright, lets talk about what is Active STS and what we can do with it.
Active STS – MSDN say’s – “A security token service (STS) is the service component that builds, signs, and issues security tokens according to the WS-Trust and WS-Federation protocols.”
The definition is really true, STS based authentication decouple the authentication logic from application, now application is responsible for authorizing the user based on Claims provided by STS and focus on application logic.
Federation where user is authenticated by one STS but they can have access to resources in other application if there is a trust relationship between the STS consumed by other application.
STS is build on top of WS-Trust specification, it describes issuing, renewing and validating of security tokens and broker the trust relationship between the participant in a secure message communication. Based on WS-Trust specification, Message is sent in the form of “Request for Security Token” and response is return in the form of “Request for Security Token Response”. For RST & RSTR, please refer the details here.
Building an Active STS
An Active-STS is consist of following
- A custom class derived from SecurityTokenService
- A WCF Service to host the SecurityTokenService
- Few Helper classes
- A MVC Client which will use the STS for authentication
SecurityTokenService
For Active-STS, most of the functionality is centered around the inherited class SecurityTokenService, it has two abstract method which needs to implemented in order to implement the functionality.
- GetScope
- GetOutputClaimsIdentity
This method checks if the requester is recognized by the STS and set Signin credential for RSTR.
This method gets called from Claims issuance pipeline when a client (implementation of IWSTrustChannelContract) request the token through Issue method (See the code – LoginService in StsClient). This method takes an incoming claims principal and return a new ClaimsIdentity with added claims.
These two method can be extended to plug-in multiple relying party client.
WCF Host and Configure
In order to host this service with WCF, an extension of WSTrustServiceHostFactory is required, which will create the service host to service the request. This is normal practices as like any other WCF Service host except, you are inheriting from WSTrustServiceHostFactory than ServiceHostFactory.
Couple of standard configuration required in WCF Service web.config file to make it working
- microsoft.identityModel section configuration in configSections and section configuration
- serviceBehaviour and bindings configuration in serviceModel.
- service certificate configuration, for this sample I am using localhost default certificate both for client and server.
I ran into issues while using the user name and password to validate the requesting client and seems only workaround to pass that is to create a derived implementation of UserNameSecurityTokenHandler, also before adding this configuration default TokenHandler – WindowsUserNameSecurityTokenHandler needs to removed.
<securityTokenHandlers> <remove type="Microsoft.IdentityModel.Tokens.WindowsUserNameSecurityTokenHandler, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add type="CPrakash.Security.ActiveSTS.RealmSTS.CustomUserNameSecurityTokenHandler, CPrakash.Security.ActiveSTS.RealmSTS" /> </securityTokenHandlers>
RP Client
In my example, I have taken MVC as a client application but it can be any application eg. WebForms. In the example application, I removed the Membership method calls and added a LoginService which has custom code to call the STS from code with the supplied credential.
SessionSecurityToken sessionToken; if (new StsClient.Services.LoginService().ValidateUser(model.UserName, model.Password, out sessionToken)) { ... // code after validation
If you look at the LoginService code, ValidateUser method takes supplied user name and password and pass it as credential to WSTrustChannelFactory.
SessionSecurityToken sessionToken; var factory = new WSTrustChannelFactory( new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential), new EndpointAddress("https://localhost/ActiveSTS/SecurityTokenService.svc")); factory.Credentials.SupportInteractive = false; factory.Credentials.UserName.UserName = userId; factory.Credentials.UserName.Password = password;
It builds the RST (request for secured token) and calls the issue method to request the secured token from service.
var rst = new RequestSecurityToken { RequestType = RequestTypes.Issue, AppliesTo = new EndpointAddress("https://localhost/stsclient/"), KeyType = KeyTypes.Bearer, TokenType = Microsoft.IdentityModel.Tokens.SecurityTokenTypes.Saml11TokenProfile }; var channel = factory.CreateChannel(); var genericToken = channel.Issue(rst) as System.IdentityModel.Tokens.GenericXmlSecurityToken;
Once we have the token retrieved from service, we can create the session token out of it and it will be used to validate the user in subsequent request.
// parse token var handlers = FederatedAuthentication.ServiceConfiguration.SecurityTokenHandlers; var token = handlers.ReadToken(new XmlTextReader(new StringReader(genericToken.TokenXml.OuterXml))); var identity = handlers.ValidateToken(token).First(); // create session token sessionToken = new SessionSecurityToken(ClaimsPrincipal.CreateFromIdentity(identity));
Last piece to write the session token to client machine using cookie through WIF’s SessionAuthenticationModule‘s WriteSessionTokenToCookie method.
FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionToken); Thread.CurrentPrincipal = sessionToken.ClaimsPrincipal;
Source code for this project is available at GitHub
Pingback: Policy Based Authorization in Windows Identity Foundation (WIF) – Part I | cprakash
This is an amazing collection of knowledge and research. Thanks for this. One thing I had to get past was that the thumbprint (located in one of the web.configs) for the localhost x509 cert needs to be unique to your dev machine, as its obviously localhost (used this to find https://www.sslshopper.com/move-or-copy-an-ssl-certificate-from-a-windows-server-to-another-windows-server.html). It took me a good while to make sense of the errors I was seeing, hopefully this helps anyone else having the same issue.